Introduction

This blog introduces sending periodical e-mail reminders in Alfresco Activiti Workflows.

Workflow with periodical e-mail reminders

To send periodical e-mail reminders (as well as to do any other periodical action) we have to start from defining the workflow. Let’s assume that we extend already existing workflow ‘Review And Approve Activiti Process’ defined in file ‘review.bpmn20.xml’. We will add periodical reminder (‘E-mail Reminder’) to review task to remind user that this task is pending by sending him e-mail.

E-mail Reminders Workflow

Corresponding XML looks like follows. Two tasks were added to it:

  • Timer, which is boundaryEvent with id ‘timer_review’
  • E-mail Reminder, which is serviceTask with id ‘mailtask_review’
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://alfresco.org">
  <process id="activitiReview" name="Review And Approve Activiti Process" isExecutable="true">
 
    <startEvent id="start" activiti:formKey="wf:submitReviewTask"></startEvent>
 
    <sequenceFlow id="flow1" sourceRef="start" targetRef="reviewTask"></sequenceFlow>
 
    <userTask id="reviewTask" name="Review Task" activiti:assignee="${bpm_assignee.properties.userName}" activiti:formKey="wf:activitiReviewTask">
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>
                if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate
                if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
        <activiti:taskListener event="complete" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>
                execution.setVariable('wf_reviewOutcome', task.getVariable('wf_reviewOutcome'));
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <sequenceFlow id="flow2" sourceRef="reviewTask" targetRef="reviewDecision"></sequenceFlow>
 
    <exclusiveGateway id="reviewDecision" name="Review Decision"></exclusiveGateway>
 
    <sequenceFlow id="flow3" sourceRef="reviewDecision" targetRef="approved">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${wf_reviewOutcome == 'Approve'}]]></conditionExpression>
    </sequenceFlow>
 
    <sequenceFlow id="flow4" sourceRef="reviewDecision" targetRef="rejected"></sequenceFlow>
 
    <userTask id="approved" name="Document Approved" activiti:assignee="${initiator.exists() ? initiator.properties.userName : 'admin'}" activiti:formKey="wf:approvedTask">
      <documentation>The document was reviewed and approved.</documentation>
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>
                if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate
                if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <userTask id="rejected" name="Document Rejected" activiti:assignee="${initiator.exists() ? initiator.properties.userName : 'admin'}" activiti:formKey="wf:rejectedTask">
      <documentation>The document was reviewed and rejected.</documentation>
      <extensionElements>
        <activiti:taskListener event="create" class="org.alfresco.repo.workflow.activiti.tasklistener.ScriptTaskListener">
          <activiti:field name="script">
            <activiti:string>
                if (typeof bpm_workflowDueDate != 'undefined') task.dueDate = bpm_workflowDueDate
                if (typeof bpm_workflowPriority != 'undefined') task.priority = bpm_workflowPriority;
            </activiti:string>
          </activiti:field>
        </activiti:taskListener>
      </extensionElements>
    </userTask>
 
    <sequenceFlow id="flow5" sourceRef="approved" targetRef="end"></sequenceFlow>
 
    <sequenceFlow id="flow6" sourceRef="rejected" targetRef="end"></sequenceFlow>
 
    <endEvent id="end"></endEvent>
 
    <boundaryEvent id="timer_review" name="Timer" attachedToRef="reviewTask" cancelActivity="false">
        <timerEventDefinition>
            <timeCycle>0 0 0 ? * MON</timeCycle>
    </timerEventDefinition>
    </boundaryEvent>
 
    <serviceTask id="mailtask_review" name="E-mail Reminder" activiti:class="org.alfresco.repo.workflow.activiti.script.AlfrescoScriptDelegate">
         <extensionElements>
             <activiti:field name="script">
                 <activiti:string>
 
                     logger.log("E-mail reminder - review task");
 
                     // Send e-mail reminder if assignee defined
                     if (bpm_assignee != null) {
                         var mail = actions.create("mail");
                         mail.parameters.to = bpm_assignee.properties.userName;
                         mail.parameters.subject = "Task reminder - document " + bpm_workflowDescription + " pending review";
                         mail.parameters.ignore_send_failure = true;
                         mail.execute(companyhome);
                     }
 
                 </activiti:string>
             </activiti:field>
         </extensionElements>
    </serviceTask>
 
    <sequenceFlow id="flow7" sourceRef="timer_review" targetRef="mailtask_review"></sequenceFlow>
 
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_activitiReview">
    <bpmndi:BPMNPlane bpmnElement="activitiReview" id="BPMNPlane_activitiReview">
      <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
        <omgdc:Bounds height="35.0" width="35.0" x="30.0" y="200.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewTask" id="BPMNShape_reviewTask">
        <omgdc:Bounds height="55.0" width="105.0" x="125.0" y="190.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="reviewDecision" id="BPMNShape_reviewDecision">
        <omgdc:Bounds height="40.0" width="40.0" x="290.0" y="197.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="approved" id="BPMNShape_approved">
        <omgdc:Bounds height="55.0" width="105.0" x="390.0" y="97.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="rejected" id="BPMNShape_rejected">
        <omgdc:Bounds height="55.0" width="105.0" x="390.0" y="297.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
        <omgdc:Bounds height="35.0" width="35.0" x="555.0" y="307.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="timer_review" id="BPMNShape_timer_review">
        <omgdc:Bounds height="30.0" width="30.0" x="175.0" y="230.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="mailtask_review" id="BPMNShape_mailtask_review">
        <omgdc:Bounds height="55.0" width="105.0" x="137.0" y="297.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="65.0" y="217.0"></omgdi:waypoint>
        <omgdi:waypoint x="125.0" y="217.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="230.0" y="217.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="217.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="310.0" y="197.0"></omgdi:waypoint>
        <omgdi:waypoint x="310.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="390.0" y="124.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="310.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="310.0" y="324.0"></omgdi:waypoint>
        <omgdi:waypoint x="390.0" y="324.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="495.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="572.0" y="124.0"></omgdi:waypoint>
        <omgdi:waypoint x="572.0" y="307.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="495.0" y="324.0"></omgdi:waypoint>
        <omgdi:waypoint x="555.0" y="324.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="190.0" y="260.0"></omgdi:waypoint>
        <omgdi:waypoint x="189.0" y="297.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

Timer

Complete reference to boundaryEvent can be found here. In our case we care about two attributes:

  • attachedToRef – indicates task which starts timer
  • cancelActivity – if set to ‘false’ does not cancel original task (defined in attachedToRef attribute)

Complete reference to timerEventDefinitions can be found here. In our case we selected to use timeCycle element, which specifies repeating interval and can be given as cron expression, e.g., run ‘E-mail Reminder’ task every Monday at 00:00am – 0 0 0 ? * MON

E-mail Reminder

This task is serviceTask, which means that it executes without any user interaction. In our case we define it as ‘org.alfresco.repo.workflow.activiti.script.AlfrescoScriptDelegate’ class to make it possible to write JavaScript code, which uses Alfresco JavaScript API. In this task, simply send e-mail with reminder to user, which was selected as review assignee.


How many lines of code does Moodle have?
The short answer is: core Moodle 2.6 has about 600k lines of code + 3rd party libraries.

Now, the long answer.
A lot of the Moodle code are 3rd party libraries that are copied mostly under “lib” directory and some other places – I have counted those separately. I have also not counted *-min.js and *-debug.js files as duplicates.

After stripping from those, here is the statistic for the core code for the latest Moodle (2.7dev build: 20131122):

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                           4462         125112         285162         564609
Javascript                     154           3856          10191          25478
CSS                            223           3411           1895          23375
XSD                             77           2304           4377          20071
XML                            133             43            127          15366
LESS                            25            443            407           7967
HTML                            46            268              7           3165
Perl                             2             57             76            535
MXML                             1             44             64            404
XSLT                             2             11             12            130
SQL                              1             26             31             92
DTD                              1              9              0             39
ActionScript                     1              6              1             33
-------------------------------------------------------------------------------
SUM:                          5128         135590         302350         661264
-------------------------------------------------------------------------------

Third party libraries account for:

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                           1747          48352         176311         285443
Javascript                    1107          57787         145980         224760
CSS                            314           3290           3897          16268
HTML                            53            583             18           6242
XML                             23            553            237           5890
LESS                            41            810            942           4406
XSD                              5            108              6           1058
XSLT                             6            176             46            800
Java                             1             74             78            230
DTD                              2              0              0             82
-------------------------------------------------------------------------------
SUM:                          3299         111733         327515         545179
-------------------------------------------------------------------------------

Have a look at the following code:

class A
{
    private $var = 17;

    public function accessOtherPrivate(A $a)
    {
        echo $a-&gt;var;
    }

}

$a = new A();
$b = new A();
$b-&gt;accessOtherPrivate($a);

The code above works just fine, you can access private properties of different object – as long as you’re the same class. PHP manual confirms the behaviour:

Members declared as private may only be accessed by the class that defines the member.

Java has the same behaviour.


Introduction

The purpose of this blog is to show how to enable search for multi-valued properties in advanced search form in Alfresco. Let’s assume that we are looking for authors and we want to find all the documents created by author John or Mary. This feature allows to do this in single query instead of two queries.

Solution

Multi-valued search is already supported in Alfresco, but requires additional configuration. This can be done by adding additional hidden field to advanced search form. The field should follow the name format prop:{name of the filed with values separated by commas}-mode and defines logical operator that should be used to replace commas.

 <config evaluator="model-type" condition="cm:content">
        <forms>
            <!-- Search form -->
            <form id="search">
                <field-visibility>
 
                    <!-- Author field-->
                    <show id="cm:author" force="true" />
                    <!-- Field to enable multi-value author support-->
                    <show id="prop:cm:author-mode" force="true"/>
 
                </field-visibility>
                <appearance>
 
                    <field id="cm:author" set="document_additional"/>
                    <!-- Use OR logical operator for authors -->
                    <field id="prop:cm:author-mode" set="document_additional">
                        <control template="/org/alfresco/components/form/controls/hidden.ftl">
                            <control-param name="contextProperty">OR</control-param>
                        </control>
                    </field>
 
                </appearance>
            </form>
        </forms>
    </config>

In addition, hidden.ftl form control has to be modified to support providing field value in contextProperty parameter. The changes are presented below:

<#-- Renders a hidden form field for edit and create modes only -->
<#assign fieldValue = "">
<#if field.control.params.contextProperty??>
   <#if context.properties[field.control.params.contextProperty]??>
      <#assign fieldValue = context.properties[field.control.params.contextProperty]>
   <#elseif args[field.control.params.contextProperty]??>
      <#assign fieldValue = args[field.control.params.contextProperty]>
   <#else>
       <#assign fieldValue = field.control.params.contextProperty>
   </#if>
<#elseif context.properties[field.name]??>
   <#assign fieldValue = context.properties[field.name]>
<#else>
   <#assign fieldValue = field.value>
</#if>
 
<#if form.mode == "edit" || form.mode == "create">
   <input type="hidden" name="${field.name}" 
          <#if field.value?is_number>value="${fieldValue?c}"<#else>value="${fieldValue?html}"</#if> />
</#if>

Introduction

This blog describes how to create e-mail template in Alfresco and use it for sending e-mail.

Template

E-mail templates are kept in folder Company Home/Data Dictionary/Email Templates, but in general they can be kept in any folder. Let’s assume, that in our template we want to use some variables, which values will be passed during invocation of e-mail action. Our template is saved in Company Home/Data Dictionary/Email Templates/sample_template.ftl and is defined as follows:

<html>
    <head></head>
    <body>
        Hello ${firstName}!
    </body>
</html>

Variable ‘firstName’ should be replaced with the name of user the message is send to.

E-mail send action

We will invoke e-mail sending action using Alfresco JavaScript API:

 var mail = actions.create("mail");
 
    // List of comma separated user names e-mail should be send to
    mail.parameters.to_many = admin;
    // Subject of e-mail
    mail.parameters.subject = "Sample e-mail";
 
    // Map of variables to be used in the template
    var map = new Object();
    map["firstName"] = person.properties["cm:firstName"];
 
    // Path to template to be used in e-mail
    mail.parameters.template = companyhome.childByNamePath("Data Dictionary/Email Templates/sample_template.ftl");
    // Map of variables to be used in the template
    mail.parameters.template_model = map;
 
    // Execute e-mail send action
    mail.execute(companyhome);


Introduction

Imagine that you have few developers working on Java project. Code of the project is committed to GIT repository and there are ANT scripts implemented to facilitate builds and deployments of the system as well as additional activities that might be useful. The developers using this infrastructure push code changes to GIT repository and automatic build and deployment of the latest code version should be automatically deployed on Tomcat server. This blog describes simple automatic build system that users GIT hooks to invoke appropriate ANT tasks depending on files that were changed in the system.

Users

There are two users that are crucial to run the automatic builds:

  • git – this user is owner of GIT repository and the commits are made as git user
  • tomcat – this user should run Tomcat

To allow the deployments, which involve stopping and starting Tomcat server, git user should be able to run tomcat as tomcat user. This functionality can be enabled in /etc/sudoers file.
Run:

sudo visudo

and add the following line

git ALL = (tomcat) NOPASSWD: ALL

To run a command as Tomcat user type

sudo -u tomcat {command name}

If you set some variables you might want not to reset them once you run sudo command. To do so add variable names to sudoers file

Defaults        env_keep += "JAVA_HOME"

Project structure

Let’s assume that our project has the following structure:

  • project – source of the project to be compiled and deployed
    • src – source code

  • configuration – Tomcat configuration files
  • git – GIT configuration files
    • post-receive – script to be invoked after push is made to GIT repository
  • build.xml – ant build script

Ant script

Let’s assume that ant script has following targets defined:

  • build-deploy – build the project and deploy it on Tomcat server
  • deploy-configuration – deploy Tomcat configuration

Depending on code changes the following targets should be run:

  • on change in project/src folder build-deploy tasks should run
  • on change in configuration folder deploy-configuration task should run

Git repository

Hooks

Git allows to run actions on several events, which mechanism is implemented using hooks. There are several hooks defined in git. Some of them are client side and some of them are server side. More information regarding hooks can be found here

Bare git repository is located on the server. Hooks are located in {git repository path}/hooks/. We are going to use server-side post-receive hook to make automatic builds. The post-receive hook runs after the entire process is completed and can be used to update other services or notify users. It takes a list of references that are being pushed from stdin.

The hooks will run as user which logs into GIT therefore it is important to ensure that tasks can be run as this user or user can run tasks as other user.

Configuration

To use ant build file (build.xml) and post-receive script, the checkout of the repository has to be done on the same server:

git clone {local path to repository}

The checkout should be done as tomcat user.

Each time push will be made to the repository, the checkout repository should be updated (git pull) and then the files from the repository could be used for builds. Having both files in the repository (build.xml and post-receive) allows dynamic configuration changes and flexible build system without a need to make any changes on the server.

post-receive hook configuration

We can invoke post-receive script which is in our git repository by invoking the script from post-receive hook in GIT repository. To do so post-receive file should be created in {git repository path}/hooks/ and made executable. The file, which is in fact bash script, should include the following commands:

#!/bin/sh

TEMP_GIT_FILE="/tmp/git.txt"
# Path to cloned version of git repository
GIT_CLONE="/opt/git-clone"

cd "$GIT_CLONE"
# Update cloned version of git repository as tomcat user
sudo -u tomcat git pull &gt; /dev/null

rm $TEMP_GIT_FILE

while read line
do
  echo $line &gt;&gt; $TEMP_GIT_FILE
done

# Run post-receive script located in git repository
cat $TEMP_GIT_FILE | ./git/post-receive

Automatic build script – post-receive

The following listing presents automatic build script. The script is located in GIT repository in git folder, so whenever it is changed the changes are automatically applied. The comments regarding parts of the script are within the code.

#!/bin/sh

# Log all the messages to the file
exec &gt;&gt; /opt/logs/git.log

# Path variables
TOMCAT="/opt/tomcat"
GIT="/opt/git"
GIT_CLONE="/opt/git-clone"

# Export variables
export JAVA_HOME="/opt/java/jdk1.7.0_07"
export PATH="$PATH":"$JAVA_HOME/bin"
export ANT_HOME="/opt/ant/apache-ant-1.8.4"
export PATH="$PATH":"$ANT_HOME/bin"
export TOMCAT_HOME=$TOMCAT

# Global variables to track code changes
configuration_change_global=1
code_change_global=1

echo "***********************************************************"
echo "BUILD"
echo $(date)
echo "***********************************************************"

cd "$GIT"

# Variables given 
while read oldrev newrev refname
do

  # Commit
  commit=$(git show $newrev | grep 'commit')
  # Date of commit
  date=$(git show $newrev | grep 'Date:')
  # Author of commit
  author=$(git show $newrev | grep 'Author:')
  
  echo "-----------------------------------------------------------" 
  echo "$commit" 
  echo "$date" 
  echo "$author" 
  echo "-----------------------------------------------------------"  
  
  # Code changes since last commit 
  git diff --name-only $newrev $oldrev | grep 'project/src' &gt; /dev/null
  code_change=$? 
  git diff --name-only $newrev $oldrev | grep 'configuration' &gt; /dev/null
  configuration_change=$? 
  
  if [ $code_change -eq 0 ]  
  then 
    code_change_global=0 
    echo "Source code changed" 
  fi 
  if [ $configuration_change -eq 0 ]  
  then 
    configuration_change_global=0 
    echo "Configuration changed" 
  fi  
done 
 
# Stop Tomcat 
cd "$TOMCAT/bin" 
sudo -u tomcat ./shutdown.sh
sleep 15 
 
pid=$(sudo -u tomcat ps aux | grep "tomca[t]" | awk '{print $2}') 
if [ -n "$pid" ]
then
    sudo -u tomcat kill -9 $pid
    sleep 5
fi

echo ""

cd "$GIT_CLONE"
sudo -u tomcat git pull  &gt; /dev/null

echo "***********************************************************"
if [ $code_change_global -eq 0 ]
then
  echo ""
  echo "COMPILE AND DEPLOY CODE"
  sudo -u tomcat ant build-deploy
  success=$?
  if [ $? -eq 0 ]
  then
    echo "SUCCESSFUL"
  else
    echo "FAILED"
  fi
  echo "-----------------------------------------------------------"  
fi
if [ $configuration_change_global -eq 0 ] 
then
  echo ""
  echo "DEPLOY CONFIGURATION"
  sudo -u tomcat ant deploy-configuration
  success=$?
  if [ $? -eq 0 ]
  then
    echo "SUCCESSFUL"
  else
    echo "FAILED"
  fi
  echo "-----------------------------------------------------------"  
fi
  
echo ""   
  
#Start Tomcat  
cd "$TOMCAT/bin"  
sudo -u tomcat ./startup.sh 

Bash and zsh both have a very neat feature for expanding a single word into more: brace expansion.
For the simplest example, imagine you need to move file.old as file.new. Normally you type:

$ mv file.old file.new

Brace expansion can save you a little bit of typing:

$ mv file.{old,new}

The command actually executed will be identical to the previous one, “file.{old,new}” gets converted into “file.old file.new”.

You can also provide a number range inside the brace:

% echo test{1..13}
test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 test12 test13

With a third parameter, the number will be increased by that much each time (step):

% echo test{1..13..2}
test1 test3 test5 test7 test9 test11 test13

Only a zsh feature is padding. You can add leading zeros to generated number to make them all the same (character) size, by adding zero(s) to the third parameter:

 % echo test{1..100..010} 
test001 test011 test021 test031 test041 test051 test061 test071 test081 test091

Zsh offers even more flexibility after setting BRACE_CCL option – you can use range of characters as well:

% setopt BRACE_CCL
% echo test{a-d} 
testa testb testc testd

I needed to locate current user’s home directory, to find and parse a config file like ~/config.ini. The current user is a user that is currently running a PHP CLI script. I have found a neat solution in drush, ready to copy & paste into your function:

  // getenv('HOME') isn't set on windows and generates a Notice.
  $home = getenv('HOME');
  if (empty($home)) {
    if (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) {
      // home on windows
      $home = $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'];
    }
  }
  return empty($home) ? NULL : $home;

Normally, when you want to redirect (standard) output of a script to a file, you run it with a redirection, like:

$ ./script.sh > out.log

However – you can also accomplish the same from inside the script by using exec. The following will write “Test” and then current time into /tmp/out.log”

#!/bin/bash
 
exec >/tmp/out.log
echo Test
date

You can find more information about exec on tldp.