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

<channel>
	<title>PHZ.FI - Sustainable IT Services</title>
	<atom:link href="http://www.phz.fi/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phz.fi</link>
	<description>Pharazon AB Long Life IT Services, Solutions and Consulting</description>
	<lastBuildDate>Mon, 14 May 2012 11:04:03 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Custom PuTTY to avoid UX inconvenience</title>
		<link>https://www.phz.fi/2012/05/14/custom-putty-to-avoid-ux-inconvenience/</link>
		<comments>https://www.phz.fi/2012/05/14/custom-putty-to-avoid-ux-inconvenience/#comments</comments>
		<pubDate>Mon, 14 May 2012 10:50:00 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[SSH PuTTY Windows UX]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=397</guid>
		<description><![CDATA[We did a few patches to PuTTY SSH client to reduce user experience (UX) inconveniences for heavy duty users (like us . The problem rise when you are using the old (0.62) version on a laptop, 3G connections and have &#8230; <a href="https://www.phz.fi/2012/05/14/custom-putty-to-avoid-ux-inconvenience/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>We did a few patches to <a title="PuTTY" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html" target="_blank">PuTTY </a>SSH client to reduce user experience (UX) inconveniences for heavy duty users (like us <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . The problem rise when you are using the old (0.62) version on a laptop, 3G connections and have connections open to 20+ servers. When you sleep or hibernate your laptop, all the connections drop and previously you couldn&#8217;t distinguish which session was which to reconnect (all 20+ windows just said &#8220;inactive&#8221;). In addition, reconnecting the sessions was previously a burden. However, we fixed it <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>1) You can now reconnect an inactive session in PuTTY by pushing enter, like on <a href="http://www.ssh.com/" target="_blank">SSH Tectia</a> client.</p>
<p>2) When the session disconnects, you don&#8217;t get anymore the annoying OK -dialog.</p>
<p>3) The title of the window shows the IP of the server also when the session is inactive, unlike before.</p>
<p>You can download our custom build PuTTY from <a href="http://files.phz.fi/putty.zip" target="_blank">here</a>. It&#8217;s build based on the 0.62 sources, compiled by PHZ.fi using Visual Studio 2010.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2012/05/14/custom-putty-to-avoid-ux-inconvenience/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu X Server does not start</title>
		<link>https://www.phz.fi/2012/03/27/381/</link>
		<comments>https://www.phz.fi/2012/03/27/381/#comments</comments>
		<pubDate>Tue, 27 Mar 2012 20:47:29 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Ubuntu nvidia xserver]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=381</guid>
		<description><![CDATA[I upgraded my Ubuntu to 11.04 but unfortunately the X did not start anymore. I got the Login screen up, but after logging in the xserver seemed to crash and return to the login screen. After upgrading to kernel 3.0.0.13 &#8230; <a href="https://www.phz.fi/2012/03/27/381/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I upgraded my Ubuntu to 11.04 but unfortunately the X did not start anymore. I got the Login screen up, but after logging in the xserver seemed to crash and return to the login screen. After upgrading to kernel 3.0.0.13 or higher I was also unable to switch to the console mode by ctrl-alt-F7 / F6 due to non-supported screen resolution (this could be fixed by uncommenting from /etc/default/grub #GRUB_TERMINAL=console and then running sudo update-grub) . I tried a bunch of different tricks such as ones described <a href="http://ubuntuforums.org/showthread.php?t=1744243">here</a> but without success. I was looking to the</p>
<p>grep &#8220;(EE)&#8221; /var/log/Xorg.0.log</p>
<p>and tried to fix for example the issues with /dev/fb0 not found, drivers noveau and nv missing. However, I checked my other well functioning machine, and that had the same error messages, so they were not the cause of the problem.</p>
<p>Finally I also took a look in the other log files and found the problem:<br />
Mar 27 23:29:29 server pulseaudio[1789]: [autospawn] core-util.c: Failed to create random directory /tmp/pulse-oLhPPjki4YeE: Permission denied<br />
Mar 27 23:29:29 server pulseaudio[1789]: [autospawn] core-util.c: Failed to symlink /var/lib/lightdm/.pulse/444ae23e9ad59364ceaf30c200000006-runtime.tmp: Permission denied</p>
<p>The /tmp dir had too strict permissions! This was fixed quickly by:<br />
sudo chmod a+w /tmp</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2012/03/27/381/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Understanding the WIP Limit and Impediments in Scrum</title>
		<link>https://www.phz.fi/2012/02/23/understanding-the-wip-limit-and-impediments-in-scrum/</link>
		<comments>https://www.phz.fi/2012/02/23/understanding-the-wip-limit-and-impediments-in-scrum/#comments</comments>
		<pubDate>Thu, 23 Feb 2012 09:54:14 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Software Engineering]]></category>
		<category><![CDATA[lean]]></category>
		<category><![CDATA[Scrum]]></category>
		<category><![CDATA[WIP]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=369</guid>
		<description><![CDATA[Scrum and Agile software development is actually based on Lean production management and Queueing Theory, having a mathematically proven basis. Understanding the basic queueing model concepts leads in better understanding of Scrum practices. Historically Lean manufacturing was based on Fredrick Taylor&#8217;s &#8230; <a href="https://www.phz.fi/2012/02/23/understanding-the-wip-limit-and-impediments-in-scrum/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Scrum and Agile software development is actually based on <a href="http://en.wikipedia.org/wiki/Lean_manufacturing" target="_blank">Lean production management</a> and <a href="http://en.wikipedia.org/wiki/Queueing_theory" target="_blank">Queueing Theory</a>, having a mathematically proven basis. Understanding the basic queueing model concepts leads in better understanding of Scrum practices. Historically Lean manufacturing was based on Fredrick Taylor&#8217;s work on <a href="http://en.wikipedia.org/wiki/Scientific_management" target="_blank">Scientific Management</a> in the 1910&#8242;s , Henry Ford&#8217;s Mass Production and in the 60&#8242;s <a href="http://en.wikipedia.org/wiki/Toyota_Production_System" target="_blank">Toyota Production System</a>. In Computer Science the Lean principles seem to lag 20 years the manufacturing, but now it&#8217;s time to wipe the dust off the 80&#8242;s Lean books (like <a href="http://en.wikipedia.org/wiki/The_Goal_(novel)" target="_blank">The Goal</a> ).</p>
<p><strong>Why WIP should be limited?</strong></p>
<p>The key adaptation of software engineering agile methods to the traditional Lean is introduction of Iterations or Sprints to reduce the Work In Progress (WIP). While in manufacturing the work pieces are constant and multiple, in software engineering the task sizes can vary wildly from half an hour to several months. The idea of the iterations is to detect the too large tasks and split them into smaller, more manageable pieces. Scrum uses estimation methods like <a href="http://en.wikipedia.org/wiki/Planning_poker" target="_blank">Planning Poker</a> and Sprint Planning Meeting to detect stories that are too large to fit in one sprint. A too large story should be split into two smaller stories. The key concept that is often missed is the Project Velocity, which means the number of tasks completed in the previous sprint. If you got 6 stories done previously, you should WIP Limit the backlog for the next sprint to 6 stories (or story points). If the practices of Project Velocity WIP Limit on Backlog and splitting of the too large tasks is taken lightly, the danger is that the team fails to deliver anything at all (I&#8217;ve seen it actually to happen).</p>
<p><strong>The Optimum WIP Limit = 1</strong></p>
<p>A mathematical proof applies to Scrum WIP Limitation stating that the optimum WIP limit = 1 task per coder (or tester, or analyst) called <a href="http://en.wikipedia.org/wiki/Little's_law">Little&#8217;s Law</a> :</p>
<p><dfn style="margin-left: 20px;">L = λ W</dfn>, where</p>
<p>L = WIP (number of tasks in a system)<br />
<em>λ</em> = Project Velocity (arrival rate of tasks or throughput)<br />
W = average time to complete one task (cycle time)</p>
<p>It&#8217;s maybe more intuitive to relabel the formula as</p>
<p><dfn style="margin-left: 20px;">Cycle_Time = WIP / Velocity</dfn></p>
<p>This has some profound implications, since we can see that</p>
<ul>
<li>if WIP = 4, the Cycle Time to complete one task is Cycle_Time = 4 / Velocity.</li>
<li>however, if we limit the WIP = 1, the Cycle time is Cycle_Time = 1 / Velocity, <em>which is four times smaller</em> than when WIP=4.</li>
</ul>
<p>The Little&#8217;s Law says that the optimum WIP is 1 to the tasks completed in the fastest possible time. In other words, you should do only one task at a time for maximum performance, multi-tasking makes your total productivity slower. If you have two coders, the WIP Limit can be 2. Having more than one coder leads into interesting increases in working efficiency that can be modeled by the <a href="http://en.wikipedia.org/wiki/Erlang_(unit)#Erlang_B_formula" target="_blank">Erlang&#8217;s Blocking Formula</a> (I&#8217;ll write more on this later).</p>
<p><strong>Impediments are not WIP</strong></p>
<p>Another concept that is widely not understood (or not used) is the <a href="http://msdn.microsoft.com/en-us/library/ff731580.aspx" target="_blank">Impediment</a>, or a blocker that prevents you from proceeding a task. It has to be stated that almost none of the Task Management softwares support management of Blockers in a proper fashion, except our pure lean task management application <a href="http://tee.do" target="_blank">http://tee.do</a> , which was specially designed to support the Lean process. For proper management of WIP Limit and for maximum performance, the impediments must be removed from the WIP queue, and placed in a separate queue, or state. When a coder stumbles into a blocker (e.g. lack of specification, servers are down etc), he should tag the task as Impediment, and start working the next highest prioritized task on the TODO backlog.</p>
<p>The key top priority task of the Scrum Master is actually to daily fight to solve the blockers as quickly as possible, by facilitating the customer to give more accurate specification, buying licences, fixing the version control etc. This is actually the same process than managing the <a href="http://en.wikipedia.org/wiki/Critical_path_method" target="_blank">Critical Path</a> in traditional project management, except the problems are not analyzed in advance, but reacted to as they occur (maybe there would be space for risk management planning in agile, too).</p>
<p>By separating WIP tasks from Blocked tasks, the WIP Limit can be adhered and the maximum performance of the team maintained. When an impediment is resolved, it should be placed back on the top of the TODO Backlog as the highest priority, to ensure that the task currently in WIP can be completed in minimum cycle time. In some situations the blocked tasks are of so high priority that they need to be worked immediately when the Impediment is resolved, but this leads into reduction of total efficiency and increased cycle time.</p>
<p><strong>Reducing Waste by Releasing Often</strong></p>
<p>The concept of WIP Limit is actually one key method to Reducing Waste in <a href="http://en.wikipedia.org/wiki/Total_quality_management" target="_blank">Total Quality Management</a>. The idea is to minimize the working capital employed by the unfinished work. In manufacturing this means reducing the size of the warehouse, but in coding it means Releasing Often and using methods like Continuous Integration. The Release Cycle of one year leads into the waste of paying the salaries of the coders for 12 months without having the system in productive use (can be hundreds of thousands or more). On the other end of the spectrum is Kanban and releasing upgrades to the software several times per day, immediately when a task is completed and tested (reducing the waste to only tens or hundreds of EUR/USD).</p>
<p><strong>Summary</strong></p>
<p>To understand the practices of Agile ways, it is beneficial to know the basic principles of the Queuing Theory. The Little&#8217;s Law states that the you should limit your work in progress to 1 task at a time per coder. To achieve the maximum productivity you should do only one task at a time. It is crucial to also understand that if the progress of the task is blocked, it should be removed from the WIP list, and have the Scrum Master to resolve the Impediment as quickly as possible. The maximum total efficiency of a software engineering process can be achieved by applying the same principle also on the big level by releasing the feature immediately once it has been completed, even several times per day.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2012/02/23/understanding-the-wip-limit-and-impediments-in-scrum/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Jenkins git remote slave problem</title>
		<link>https://www.phz.fi/2011/12/07/jenkins-git-remote-slave-problem/</link>
		<comments>https://www.phz.fi/2011/12/07/jenkins-git-remote-slave-problem/#comments</comments>
		<pubDate>Wed, 07 Dec 2011 09:32:05 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Software Engineering]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[GIT]]></category>
		<category><![CDATA[Jenkins]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=345</guid>
		<description><![CDATA[Today I came up with an issue with Jenkins Continuous Integration (v.1.412) server when I was trying to fetch a git repository on a remote slave server (Red Hat). I found a few similar issues and open bugs, but none &#8230; <a href="https://www.phz.fi/2011/12/07/jenkins-git-remote-slave-problem/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Today I came up with an issue with Jenkins Continuous Integration (v.1.412) server when I was trying to fetch a git repository on a remote slave server (Red Hat). I found a few similar <a href="http://stackoverflow.com/questions/3516719/hudson-git-error-ssh">issues</a> and open <a href="https://issues.jenkins-ci.org/browse/JENKINS-8149">bugs</a>, but none of them were directly related to Linux slaves. I got this error message dump:</p>
<p>Started by user jenkins<br />
Building remotely on server01<br />
Checkout:MyProject / /home/jenkins/workspace/MyProject &#8211; hudson.remoting.Channel@5352c503:server01<br />
Using strategy: Default<br />
Last Built Revision: Revision ebb0c40a1a321a00d8176e25aa81364efaac702f (origin/master)<br />
Checkout:MyProject / /home/jenkins/workspace/MyProject &#8211; hudson.remoting.LocalChannel@59ab12f8<br />
Fetching changes from 1 remote Git repository<br />
Fetching upstream changes from ssh://git.server/var/repos/git/MyProject<br />
ERROR: Problem fetching from origin / origin &#8211; could be unavailable. Continuing anyway<br />
ERROR: (Underlying report) : Error performing command: git fetch -t ssh://git.server/var/repos/git/MyProject +refs/heads/*:refs/remotes/origin/*<br />
Command &#8220;git fetch -t ssh://git.server/var/repos/git/MyProject +refs/heads/*:refs/remotes/origin/*&#8221; returned status code 128: error: cannot run ssh: No such file or directory<br />
fatal: unable to fork</p>
<p>ERROR: Could not fetch from any repository<br />
FATAL: Could not fetch from any repository<br />
hudson.plugins.git.GitException: Could not fetch from any repository<br />
at hudson.plugins.git.GitSCM$2.invoke(GitSCM.java:1008)<br />
at hudson.plugins.git.GitSCM$2.invoke(GitSCM.java:968)<br />
at hudson.FilePath$FileCallableWrapper.call(FilePath.java:1956)<br />
at hudson.remoting.UserRequest.perform(UserRequest.java:118)<br />
at hudson.remoting.UserRequest.perform(UserRequest.java:48)<br />
at hudson.remoting.Request$2.run(Request.java:270)<br />
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)<br />
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)<br />
at java.util.concurrent.FutureTask.run(FutureTask.java:166)<br />
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)<br />
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)<br />
at hudson.remoting.Engine$1$1.run(Engine.java:60)<br />
at java.lang.Thread.run(Thread.java:636)</p>
<p>The issue was resolved by adding correct location of the ssh -command (on my server /usr/bin) to the PATH of slave node&#8217;s environment settings (on Jenkins).</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2011/12/07/jenkins-git-remote-slave-problem/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Debian install-info.postinst: update-info-dir: not found</title>
		<link>https://www.phz.fi/2011/11/13/debian-install-info-postinst-update-info-dir-not-found/</link>
		<comments>https://www.phz.fi/2011/11/13/debian-install-info-postinst-update-info-dir-not-found/#comments</comments>
		<pubDate>Sun, 13 Nov 2011 20:49:21 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[dpkg]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=332</guid>
		<description><![CDATA[Today my Debian stable upgrade to testing went bad (read VERY BAD) by letting apt-get -f install remove packages such as libc6, linux-base, base-files and other quite important stuff Initially even /bin/sh did not work, but luckily I was able &#8230; <a href="https://www.phz.fi/2011/11/13/debian-install-info-postinst-update-info-dir-not-found/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Today my Debian stable upgrade to testing went bad (read VERY BAD) by letting apt-get -f install remove packages such as libc6, linux-base, base-files and other quite important stuff <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Initially even /bin/sh did not work, but luckily I was able to restore it by symlinking ln -s /bin/sh.distrib /bin/sh . After several hours of manually downloading packages and resolving version conflicts, I ran to this problem:</p>
<p>dpkg -i install-info_4.13a.dfsg.1-8_i386.deb<br />
(Reading database &#8230; 23530 files and directories currently installed.)<br />
Preparing to replace install-info 4.13a.dfsg.1-6 (using install-info_4.13a.dfsg.1-8_i386.deb) &#8230;<br />
Unpacking replacement install-info &#8230;<br />
Setting up install-info (4.13a.dfsg.1-8) &#8230;<br />
/var/lib/dpkg/info/install-info.postinst: 32: /var/lib/dpkg/info/install-info.postinst: update-info-dir: not found<br />
dpkg: error processing install-info (&#8211;install):<br />
subprocess installed post-installation script returned error exit status 127<br />
Processing triggers for man-db &#8230;<br />
Errors were encountered while processing:<br />
install-info</p>
<p>I googled for answers, but found none, except to reinstall the whole system:<br />
<a href="http://ubuntuforums.org/archive/index.php/t-1547223.html">http://ubuntuforums.org/archive/index.php/t-1547223.html</a></p>
<p>The solution is simpler than that. The problem in my case was that apt-get had removed bash, and update-info-dir requires /bin/bash. I executed the script</p>
<p>/usr/sbin/update-info-dir<br />
-bash: /usr/sbin/update-info-dir: /bin/bash: bad interpreter: No such file or directory</p>
<p>The apt-get error message and previous posters did not figure this out. Unfortunately apt-get install bash did not quite work, but it downloaded the package to</p>
<p>cd /var/cache/apt/archives</p>
<p>from where I was able to</p>
<p>dpkg -i bash_4.1-3_i386.deb</p>
<p>and was able to continue restoring the system <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2011/11/13/debian-install-info-postinst-update-info-dir-not-found/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to test file download by Selenium</title>
		<link>https://www.phz.fi/2011/10/05/how-to-test-file-download-by-selenium/</link>
		<comments>https://www.phz.fi/2011/10/05/how-to-test-file-download-by-selenium/#comments</comments>
		<pubDate>Wed, 05 Oct 2011 09:47:00 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Testing]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=306</guid>
		<description><![CDATA[In principle it&#8217;s a bit tricky to test file download by using PHPUnit and Selenium, because you can&#8217;t control the browser windows (such as the file download dialog) by Javascript. There have been other approaches to overcome this limitation, such &#8230; <a href="https://www.phz.fi/2011/10/05/how-to-test-file-download-by-selenium/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>In principle it&#8217;s a bit tricky to test file download by using PHPUnit and Selenium, because you can&#8217;t control the browser windows (such as the file download dialog) by Javascript. There have been other approaches to overcome this limitation, such as using <a href="http://www.jsystemtest.org/?q=node/70">AutoIT or AWT Robot/JInvoke</a> (but it restricts your tests to Windows only or installing some new tools). This generic approach works in other languages such as Java, Ruby and Python, too.</p>
<p>This approach has also some limitations, it works only in Firefox (in theory it can be used in any browser that can be configured to auto-download to a specific directory without popping up the file download dialog). This requires also some detailed configuration and modifications to the both selenium-server and browser settings, but you don&#8217;t need to install any extra libraries or tools. I figured out this test case when I was required to automatically test downloading of a CSV Excel file (or in particular test that an empty file is not downloaded).</p>
<p><strong>1) Create a Custom Firefox Profile</strong></p>
<p>I found this instruction from <a href="http://kb.mozillazine.org/File_types_and_download_actions">here</a> (you might need some alternations if you use for example Seamonkey or Firefox 2), but this worked at least on Firefox 7.0.1. First create a directory in your project root called for example Test/BrowserProfiles/firefox3.selenium/ and create a file called mimeTypes.rdf with the following contents:</p>
<pre>&lt;?xml version="1.0"?&gt;
&lt;RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"&gt;
  &lt;RDF:Description RDF:about="urn:root"
                   NC:en-US_defaultHandlersVersion="3" /&gt;
  &lt;RDF:Description RDF:about="urn:mimetype:application/vnd.ms-excel"
                   NC:value="application/vnd.ms-excel"
                   NC:editable="true"
                   NC:description="CSV document"
                   NC:alwaysAsk="false"
                   NC:saveToDisk="true"&gt;
    &lt;NC:fileExtensions&gt;csv&lt;/NC:fileExtensions&gt;
    &lt;NC:fileExtensions&gt;xls&lt;/NC:fileExtensions&gt;
    &lt;NC:fileExtensions&gt;xlb&lt;/NC:fileExtensions&gt;
    &lt;NC:fileExtensions&gt;xlt&lt;/NC:fileExtensions&gt;
    &lt;NC:handlerProp RDF:resource="urn:mimetype:handler:application/vnd.ms-excel"/&gt;
  &lt;/RDF:Description&gt;
  &lt;RDF:Description RDF:about="urn:mimetype:handler:text/csv"
                   NC:saveToDisk="true"
                   NC:alwaysAsk="false" /&gt;
  &lt;RDF:Description RDF:about="urn:mimetype:text/csv"
                   NC:fileExtensions="csv"
                   NC:description="CSV document"
                   NC:value="text/csv"
                   NC:editable="true"
                   NC:alwaysAsk="false"
                   NC:saveToDisk="true"&gt;
      &lt;NC:handlerProp RDF:resource="urn:mimetype:handler:text/csv"/&gt;
  &lt;/RDF:Description&gt;
&lt;/RDF:RDF&gt;</pre>
<p>The key thing here is to set the two properties NC:alwaysAsk=&#8221;false&#8221; and NC:savetoDisk=&#8221;true&#8221; to get the file downloads to start automatically without popping the file download dialog. You can also change and set the NC:fileExtensions and mime types (NC:Value) to other than text/csv, too, to suit your needs.</p>
<p>The <a href="http://garbuz.com/2010/07/31/running-selenium-with-custom-firefox-profile/">original approach</a> was to copy your current whole Firefox profile directory and then edit the files, but this incorporated hundreds of unnecessary files to the project. Since we have a quite strict code review process and nobody wanted to line-by-line inspect binary cache files, I figured out that you don&#8217;t actually need any other files than the mimeTypes.rdf, and that you can delete most of the contents of that file without any harm.</p>
<p><strong>2) Customize Selenium-Server to use the custom profile</strong></p>
<p>Start the selenium-server by the following command line:</p>
<pre>java -jar selenium-server-standalone-2.6.0.jar -firefoxProfileTemplate ~/workspace/myproject/test/selenium/browserProfiles/firefox3.selenium</pre>
<p>Please edit the paths and jar versions according to your own setup.</p>
<p><strong>3) Write the test case</strong></p>
<p>This is a bit simplified version of the real test case I made, for clarity. The approach is to first check from the (Firefox) Downloads/ folder that the file does not exist there yet. Then the download-button is clicked, and assuming everything is set up as described above, Firefox should download the file without asking any questions. Then you can check again the Downloads/ folder for the filename and check if it has appeared.</p>
<pre>&lt;?php
require_once('PHPUnit_Extensions_SeleniumTestCase');
class DownloadCSVTest extends PHPUnit_Extensions_SeleniumTestCase
{
    /**
     * Test Case - Try load a CSV
     * Expected: CSV file was downloaded
     * Note! This works only in Firefox and if you use the custom browser profile
     * in Test/Selenium/BrowserProfiles/firefox.selenium to download the file
     * This test assumes that your Firefox download directory is ~/Downloads/ (Linux)
     * Note! You need to launch your selenium-server to use the firefox profile found in
     * java -jar selenium-server-standalone-2.6.0.jar -firefoxProfileTemplate ~/workspace/ServiceProductionAdmin/Test/Selenium/BrowserProfiles/  firefox.selenium/
     * @see http://kb.mozillazine.org/File%5Ftypes%5Fand%5Fdownload%5Factions
     * @author Antti Hätinen &lt;antti.hatinen@phz.fi&gt;
     */
    public function testCSVDownload()
    {
	//this works only in Firefox, so use browser type *chrome
        self::$browser = "*chrome";
        //clear up
        $filename = getenv("HOME") . '/Downloads/fileToBeDownloaded.csv';
        @unlink($filename);

        //Download
        $this-&gt;focusAndClick("button:contains('Download')");
        sleep(3); //wait for download to complete
        $this-&gt;assertTrue(file_exists($filename),"CSV was not downloaded to '$filename' . Check that you have configured the Selenium-Server to use the custom firefox profile in myproject/Test/Selenium/BrowserProfiles/firefox.selenium/");

        //delete the file if it exists
        unlink($filename);
    }
}</pre>
<p>Then you can run the test</p>
<pre> phpunit --stop-on-failure --filter testCSVDownload DownloadCSVTest.php</pre>
<p>And hopefully all tests pass (sooner or later <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2011/10/05/how-to-test-file-download-by-selenium/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to integrate CakePHP to Joomla 1.6 as a component</title>
		<link>https://www.phz.fi/2011/09/16/how-to-integrate-cakephp-to-joomla-1-6-as-a-component/</link>
		<comments>https://www.phz.fi/2011/09/16/how-to-integrate-cakephp-to-joomla-1-6-as-a-component/#comments</comments>
		<pubDate>Fri, 16 Sep 2011 07:45:19 +0000</pubDate>
		<dc:creator>J Laine</dc:creator>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Software Engineering]]></category>

		<guid isPermaLink="false">http://www.phz.fi/?p=165</guid>
		<description><![CDATA[Have you ever dreamed about integrating Joomla and CakePHP together such a way, that the best sides of both the systems would be utilized most efficiently? We&#8217;ll tell you now, how to do that! Like we know, CakePHP is an &#8230; <a href="https://www.phz.fi/2011/09/16/how-to-integrate-cakephp-to-joomla-1-6-as-a-component/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Have you ever dreamed about integrating <a href="http://www.joomla.org/" target="_blank">Joomla</a> and <a href="http://cakephp.org/" target="_blank">CakePHP</a> together such a way, that the best sides of both the systems would be utilized most efficiently? We&#8217;ll tell you now, how to do that!</p>
<p>Like we know, CakePHP is an excellent tool (for example) creating <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete" target="_blank">CRUD-interfaces</a> from existing databases in a quick and easy fashion. However, it often takes too much of work when you have to code some features again and again (like image galleries, user registration system and many other things). On the other hand, Joomla is a well known content management system, that has a lot of features and plugins that are helpful when creating functional, dynamic and good-looking websites. So is there a way to make Cake and Joomla to work together?</p>
<p>Some years ago there was published an <a href="http://www.gigapromoters.com/blog/2007/02/13/finally-a-practical-solution-joomla-with-cakephp-together-jake" target="_blank">article</a> about integrating Joomla and CakePHP together (this integration was called &#8220;JAKE&#8221;). As many people (including we) found this article very useful, and since both of the systems have evolved during last years, we thought it is now a right time to update article&#8217;s technical content to cover Joomla 1.6 and CakePHP 1.3. Like in the article mentioned above, also this article is meant to inform you how to include CakePHP to Joomla as a <a href="http://docs.joomla.org/Component" target="_blank">component</a>.</p>
<p><strong>1. </strong>Start by installing CakePHP 1.3 in <strong>&#8216;\components\com_cake&#8217;</strong> of Joomla 1.6 directory. Of course, you need to create the subdirectory &#8216;<strong>com_cake</strong>&#8216; first. When CakePHP is installed, the directories <strong>app</strong>, <strong>cake</strong>, <strong>plugins</strong> and <strong>vendors</strong> should be found directly under <strong>&#8216;\com_cake&#8217;</strong> (like com_cake\app etc.)<strong>.<br />
</strong></p>
<p><strong>2. </strong>After installation you have to create the so-called triggers for the component. Without them, Joomla can&#8217;t use the component. So open your favourite text-editor and create files &#8220;<strong>cake.php</strong>&#8221; and &#8220;<strong>cake.html.php</strong>&#8221; to &#8216;<strong>components\com_cake</strong>&#8216; -directory. Put following content to the files:<strong><br />
</strong></p>
<p><strong>cake.php:</strong></p>
<pre>&lt;?php
defined( '_JEXEC' ) or die( 'Restricted access' );

require_once( JApplicationHelper::getPath('front_html') );
jimport('joomla.application.component.controller');
jimport('joomla.application.component.helper');

$document = JFactory::getDocument();
$document-&gt;setTitle("com_cake: Ultimate Joomla Component");
$joomla_path = dirname(dirname(dirname(__FILE__)));

// As this component (cakephp) will need database access, lets include Joomla's config file
require_once($joomla_path.'/configuration.php');

// Constants to be used later in com_cake
$config = new JConfig();

define(JOOMLA_PATH,JURI::base());
define(DB_SERVER,$config-&gt;host);
define(DB_USER,$config-&gt;user);
define(DB_PASSWORD,$config-&gt;password);
define(DB_NAME,$config-&gt;db);

$controller = JArrayHelper::getValue($_REQUEST ,'module'); //option passed is treated as a controller in cake
$action = JArrayHelper::getValue($_REQUEST ,'task'); //task passed is treated as a controller in cake
$param = JArrayHelper::getValue($_REQUEST ,'id');
HTML_cake::requestCakePHP('/'.$controller.'/'.$action.'/'.$param);</pre>
<p><strong>cake.html.php:</strong></p>
<pre>&lt;?php
defined( '_JEXEC' ) or die( 'Restricted access' );
class HTML_cake {
    function requestCakePHP($url) {
        $_GET['url']=$url;
        require_once 'app/webroot/index.php';
    }
}</pre>
<p>Edit also Cake&#8217;s<strong> database.php </strong>following way:</p>
<pre>class DATABASE_CONFIG
{
    var $default = array(
	'driver' =&gt; 'mysql',
	'connect' =&gt; 'mysql_connect',
	'host' =&gt; DB_SERVER,
	'login' =&gt; DB_USER,
	'password' =&gt; DB_PASSWORD,
	'database' =&gt; DB_NAME
    );
}</pre>
<p><strong>3. </strong>For next, let&#8217;s configure rewrite-rules. Joomla will now take over the URL Rewriting for CakePHP. Disable all CakePHP .htaccess files by renaming them for example as &#8220;htaccess&#8221; (without the leading dot):</p>
<p><strong> * components/com_cake/.htaccess</strong><br />
<strong> * components/com_cake/app/.htaccess</strong><br />
<strong> * components/com_cake/app/webroot/.htaccess</strong></p>
<p>Configure then App.baseURL from &#8216;<strong>components/com_cake/app/config/core.php</strong>&#8216;, by uncommenting and editing the following row:</p>
<pre>Configure::write('App.baseUrl', 'components/com_cake/app');</pre>
<p><strong>4. </strong>After that, we need to modify Cake&#8217;s helpers that are used to output links, images and other things. This must be done in order to make the helpers to output URLs in Joomla&#8217;s format:</p>
<p><strong>index.php?option=com_cake&amp;module=names&amp;task=edit&amp;id=5</strong></p>
<p>So open firstly the file &#8216;<strong>app/config/bootstrap.php</strong>&#8216; and add the following function to end of the file. It&#8217;s possible that you have to modify it afterwards (according to your application), but the function&#8217;s idea is to find individual parameters given by the user in URL (like &#8220;task=edit&#8221; etc.) and output the URL in Joomla&#8217;s format:</p>
<pre>function reform_url($url) {
    // Explode url to parts according to the / -character
    $temp=explode('/',$url);
    $controller = false;
    $action = false;
    $param = false;
    if (count($temp) &gt; 3) $controller = "&amp;module=".$temp[3];
    if (count($temp) &gt; 4) $action = '&amp;task='.$temp[4];
    if (count($temp) &gt; 5) $param = '&amp;id='.$temp[5];
    $url=JOOMLA_PATH.'index.php?option=com_cake'.$controller.$action.$param;
    return $url;
}</pre>
<p><strong>5. </strong>Then integrate the function &#8220;<strong>reform_url</strong>&#8221; with all the helper functions that process URLs, to make them output URLs in Joomla&#8217;s format. Copy file &#8216;<strong>cake/libs/view/helpers/html.php</strong>&#8216; and &#8216;<strong>form.php</strong>&#8216; to directory &#8216;<strong>app/views/helpers</strong>&#8216; and file &#8216;<strong>cake/libs/controller/controller.php&#8217; </strong>to<strong> &#8216;app/controllers</strong>&#8216;. After that, edit the following functions:<strong><br />
</strong></p>
<p><strong>html.php:</strong></p>
<pre>function link($title, $url = null, $options = array(), $confirmMessage = false) {
	$escapeTitle = true;
	if ($url !== null) {
           $url = <strong>reform_url</strong>($this-&gt;url($url));
	} else {
		$url = <strong>reform_url</strong>($this-&gt;url($title));
		$title = $url;
		$escapeTitle = false;
	}
	if (isset($options['escape'])) {
		$escapeTitle = $options['escape'];
	}
	if ($escapeTitle === true) {
		$title = h($title);
	} elseif (is_string($escapeTitle)) {
		$title = htmlentities($title, ENT_QUOTES, $escapeTitle);
	}
	if (!empty($options['confirm'])) {
		$confirmMessage = $options['confirm'];
		unset($options['confirm']);
	}
	if ($confirmMessage) {
		$confirmMessage = str_replace("'", "\'", $confirmMessage);
		$confirmMessage = str_replace('"', '\"', $confirmMessage);
		$options['onclick'] = "return confirm('{$confirmMessage}');";
	} elseif (isset($options['default']) &amp;&amp; $options['default'] == false) {
		if (isset($options['onclick'])) {
			$options['onclick'] .= ' event.returnValue = false; return false;';
		} else {
			$options['onclick'] = 'event.returnValue = false; return false;';
		}
		unset($options['default']);
	}
	return sprintf($this-&gt;tags['link'], $url, $this-&gt;_parseAttributes($options), $title);
}</pre>
<pre>function image($path, $options = array()) {
	if (is_array($path)) {
		$path = <strong>reform_url</strong>($this-&gt;url($path));
	} elseif (strpos($path, '://') === false) {
		if ($path[0] !== '/') {
			$path = IMAGES_URL . $path;
		}
		$path = $this-&gt;assetTimestamp($this-&gt;webroot($path));
	}
	if (!isset($options['alt'])) {
		$options['alt'] = '';
	}
	$url = false;
	if (!empty($options['url'])) {
		$url = $options['url'];
		unset($options['url']);
	}
	$image = sprintf($this-&gt;tags['image'], $path, $this-&gt;_parseAttributes($options, null, '', ' '));
	if ($url) {
		return sprintf($this-&gt;tags['link'], <strong>reform_url</strong>($this-&gt;url($url)), null, $image);
	}
	return $image;
}</pre>
<p><strong>form.php:</strong></p>
<pre>function create($model = null, $options = array()) {
        $created = $id = false;
        $append = '';
        $view =&amp; ClassRegistry::getObject('view');
        if (is_array($model) &amp;&amp; empty($options)) {
            $options = $model;
            $model = null;
        }
        if (empty($model) &amp;&amp; $model !== false &amp;&amp; !empty($this-&gt;params['models'])) {
            $model = $this-&gt;params['models'][0];
            $this-&gt;defaultModel = $this-&gt;params['models'][0];
        } elseif (empty($model) &amp;&amp; empty($this-&gt;params['models'])) {
            $model = false;
        }
        $models = ClassRegistry::keys();
        foreach ($models as $currentModel) {
            if (ClassRegistry::isKeySet($currentModel)) {
                $currentObject =&amp; ClassRegistry::getObject($currentModel);
                if (is_a($currentObject, 'Model') &amp;&amp; !empty($currentObject-&gt;validationErrors)) {
                    $this-&gt;validationErrors[Inflector::camelize($currentModel)] =&amp; $currentObject-&gt;validationErrors;
                }
            }
        }
        $object = $this-&gt;_introspectModel($model);
        $this-&gt;setEntity($model . '.', true);
        $modelEntity = $this-&gt;model();
        if (isset($this-&gt;fieldset[$modelEntity]['key'])) {
            $data = $this-&gt;fieldset[$modelEntity];
            $recordExists = (
                isset($this-&gt;data[$model]) &amp;&amp;
                !empty($this-&gt;data[$model][$data['key']]) &amp;&amp;
                !is_array($this-&gt;data[$model][$data['key']])
            );
            if ($recordExists) {
                $created = true;
                $id = $this-&gt;data[$model][$data['key']];
            }
        }
        $options = array_merge(array(
            'type' =&gt; ($created &amp;&amp; empty($options['action'])) ? 'put' : 'post',
            'action' =&gt; null,
            'url' =&gt; null,
            'default' =&gt; true,
            'encoding' =&gt; strtolower(Configure::read('App.encoding')),
            'inputDefaults' =&gt; array()),
        $options);
        $this-&gt;_inputDefaults = $options['inputDefaults'];
        unset($options['inputDefaults']);
        if (empty($options['url']) || is_array($options['url'])) {
            if (empty($options['url']['controller'])) {
                if (!empty($model) &amp;&amp; $model != $this-&gt;defaultModel) {
                    $options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
                } elseif (!empty($this-&gt;params['controller'])) {
                    $options['url']['controller'] = Inflector::underscore($this-&gt;params['controller']);
                }
            }
            if (empty($options['action'])) {
                $options['action'] = $this-&gt;params['action'];
            }

            $actionDefaults = array(
                'plugin' =&gt; $this-&gt;plugin,
                'controller' =&gt; $view-&gt;viewPath,
                'action' =&gt; $options['action']
            );
            if (!empty($options['action']) &amp;&amp; !isset($options['id'])) {
                $options['id'] = $this-&gt;domId($options['action'] . 'Form');
            }
            $options['action'] = array_merge($actionDefaults, (array)$options['url']);
            if (empty($options['action'][0])) {
                $options['action'][0] = $id;
            }
        } elseif (is_string($options['url'])) {
            $options['action'] = $options['url'];
        }
        unset($options['url']);

        switch (strtolower($options['type'])) {
            case 'get':
                $htmlAttributes['method'] = 'get';
            break;
            case 'file':
                $htmlAttributes['enctype'] = 'multipart/form-data';
                $options['type'] = ($created) ? 'put' : 'post';
            case 'post':
            case 'put':
            case 'delete':
                $append .= $this-&gt;hidden('_method', array(
                    'name' =&gt; '_method', 'value' =&gt; strtoupper($options['type']), 'id' =&gt; null
                ));
            default:
                $htmlAttributes['method'] = 'post';
            break;
        }
        $this-&gt;requestType = strtolower($options['type']);

        $htmlAttributes['action'] = <strong>reform_url</strong>($this-&gt;url($options['action']));
        unset($options['type'], $options['action']);

        if ($options['default'] == false) {
            if (isset($htmlAttributes['onSubmit']) || isset($htmlAttributes['onsubmit'])) {
                $htmlAttributes['onsubmit'] .= ' event.returnValue = false; return false;';
            } else {
                $htmlAttributes['onsubmit'] = 'event.returnValue = false; return false;';
            }
        }

        if (!empty($options['encoding'])) {
            $htmlAttributes['accept-charset'] = $options['encoding'];
            unset($options['encoding']);
        }

        unset($options['default']);
        $htmlAttributes = array_merge($options, $htmlAttributes);

        $this-&gt;fields = array();
        if (isset($this-&gt;params['_Token']) &amp;&amp; !empty($this-&gt;params['_Token'])) {
            $append .= $this-&gt;hidden('_Token.key', array(
                'value' =&gt; $this-&gt;params['_Token']['key'], 'id' =&gt; 'Token' . mt_rand())
            );
        }

        if (!empty($append)) {
            $append = sprintf($this-&gt;Html-&gt;tags['block'], ' style="display:none;"', $append);
        }

        $this-&gt;setEntity($model . '.', true);
        $attributes = $this-&gt;_parseAttributes($htmlAttributes, null, '');
        return sprintf($this-&gt;Html-&gt;tags['form'], $attributes) . $append;
}</pre>
<p><strong>controller.php:</strong></p>
<pre>function flash($message, $url, $pause = 1, $layout = 'flash') {

    $this-&gt;autoRender = false;
    $this-&gt;set('url', <strong>reform_url</strong>(Router::url($url)));
    $this-&gt;set('message', $message);
    $this-&gt;set('pause', $pause);
    $this-&gt;set('page_title', $message);
    $this-&gt;render(false, $layout);
}</pre>
<p>Finally, edit the file <strong>&#8216;cake/libs/router.php</strong>&#8216;:</p>
<pre>function url($url = null, $full = false) {

        $self =&amp; Router::getInstance();
        $defaults = $params = array('plugin' =&gt; null, 'controller' =&gt; null, 'action' =&gt; 'index');

        if (is_bool($full)) {
            $escape = false;
        } else {
            extract($full + array('escape' =&gt; false, 'full' =&gt; false));
        }

        if (!empty($self-&gt;__params)) {
            if (isset($this) &amp;&amp; !isset($this-&gt;params['requested'])) {
                $params = $self-&gt;__params[0];
            } else {
                $params = end($self-&gt;__params);
            }
        }
        $path = array('base' =&gt; null);

        if (!empty($self-&gt;__paths)) {
            if (isset($this) &amp;&amp; !isset($this-&gt;params['requested'])) {
                $path = $self-&gt;__paths[0];
            } else {
                $path = end($self-&gt;__paths);
            }
        }
        $base = $path['base'];
        $extension = $output = $mapped = $q = $frag = null;

        if (is_array($url)) {
            if (isset($url['base']) &amp;&amp; $url['base'] === false) {
                $base = null;
                unset($url['base']);
            }
            if (isset($url['full_base']) &amp;&amp; $url['full_base'] === true) {
                $full = true;
                unset($url['full_base']);
            }
            if (isset($url['?'])) {
                $q = $url['?'];
                unset($url['?']);
            }
            if (isset($url['#'])) {
                $frag = '#' . urlencode($url['#']);
                unset($url['#']);
            }
            if (empty($url['action'])) {
                if (empty($url['controller']) || $params['controller'] === $url['controller']) {
                    $url['action'] = $params['action'];
                } else {
                    $url['action'] = 'index';
                }
            }

            $prefixExists = (array_intersect_key($url, array_flip($self-&gt;__prefixes)));
            foreach ($self-&gt;__prefixes as $prefix) {
                if (!empty($params[$prefix]) &amp;&amp; !$prefixExists) {
                    $url[$prefix] = true;
                } elseif (isset($url[$prefix]) &amp;&amp; !$url[$prefix]) {
                    unset($url[$prefix]);
                }
                if (isset($url[$prefix]) &amp;&amp; strpos($url['action'], $prefix . '_') === 0) {
                    $url['action'] = substr($url['action'], strlen($prefix) + 1);
                }
            }

            $url += array('controller' =&gt; $params['controller'], 'plugin' =&gt; $params['plugin']);

            if (isset($url['ext'])) {
                $extension = '.' . $url['ext'];
                unset($url['ext']);
            }
            $match = false;

            for ($i = 0, $len = count($self-&gt;routes); $i &lt; $len; $i++) {                 $originalUrl = $url;                 if (isset($self-&gt;routes[$i]-&gt;options['persist'], $params)) {
                    $url = $self-&gt;routes[$i]-&gt;persistParams($url, $params);
                }

                if ($match = $self-&gt;routes[$i]-&gt;match($url)) {
                    $output = trim($match, '/');
                    break;
                }
                $url = $originalUrl;
            }

            $output = str_replace("/sort:","/sort=",$output);
            $output = str_replace("/direction:","/direction=",$output);

            if ($match === false) {
                $output = $self-&gt;_handleNoRoute($url);
            }

            $output = str_replace('//', '/', $base . '/' . $output);
            } else {
            $url = <strong>reform_url</strong>($url);
            if (((strpos($url, '://')) || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (!strncmp($url, '#', 1))) {
                return $url;
            }
            if (empty($url)) {
                if (!isset($path['here'])) {
                    $path['here'] = '/';
                }
                $output = $path['here'];
            } elseif (substr($url, 0, 1) === '/') {
                $output = $base . $url;
            } else {
                $output = $base . '/';
                foreach ($self-&gt;__prefixes as $prefix) {
                    if (isset($params[$prefix])) {
                        $output .= $prefix . '/';
                        break;
                    }
                }
                if (!empty($params['plugin']) &amp;&amp; $params['plugin'] !== $params['controller']) {
                    $output .= Inflector::underscore($params['plugin']) . '/';
                }
                $output .= Inflector::underscore($params['controller']) . '/' . $url;
            }
            $output = str_replace('//', '/', $output);
        }
        if ($full &amp;&amp; defined('FULL_BASE_URL')) {
            $output = FULL_BASE_URL . $output;
        }
        if (!empty($extension) &amp;&amp; substr($output, -1) === '/') {
            $output = substr($output, 0, -1);
        }

        return $output . $extension . $self-&gt;queryString($q, array(), $escape) . $frag;
}</pre>
<p><strong>6. </strong>Your new CakePHP -component is now ready to be tested, so open it by writing an URL like:</p>
<p><strong>http://localhost/joomla/index.php?option=com_cake</strong></p>
<p>If everything went right, you&#8217;ll see Cake&#8217;s homepage in Joomla layout. Make sure you have edited your default.html file so that CSS and HTML tags do not mess up.<strong><br />
</strong></p>
<p><strong>7. </strong>Maybe you want to distribute your new CakePHP -component for your friends or other people? Even if you don&#8217;t, it is recommended that all the components used by Joomla are installed correctly to CMS.</p>
<p>It&#8217;s quite easy task to to create an install package for your CakePHP -component. Just create an empty folder to somewhere on your disk (like &#8216;<strong>temp/com_cake</strong>&#8216;) and make there two subdirectories:<strong> &#8216;site&#8217; </strong>and<strong> &#8216;admin&#8217;</strong>. Then copy all the content from the folder in where you just created your component (like &#8216;<strong>joomla/components/com_cake</strong>&#8216;) to the new sub-directory &#8216;<strong>temp/com_cake/site</strong>&#8216;.</p>
<p>As we aren&#8217;t going to make an admin-interface for the CakePHP-component (at least, yet), just create the following two files to the folder &#8216;<strong>temp/com_cake/admin</strong>&#8216;:</p>
<p><strong>cake.php:</strong></p>
<pre>&lt;html&gt;
&lt;body&gt;
    CakePHP component v1.0
&lt;/body&gt;
&lt;/html&gt;</pre>
<p><strong>index.html:</strong></p>
<pre>&lt;html&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>Finally, create an install-file for the component. Open your favorite text-editor and create file &#8216;<strong>install.xml</strong>&#8216; to the root folder &#8216;<strong>temp/com_cake</strong>&#8216;. Add the following content to the file:</p>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;install type="component" version="1.6.2"&gt;

 &lt;name&gt;Cake&lt;/name&gt;
 &lt;!-- The following elements are optional and free of formatting constraints --&gt;
 &lt;creationDate&gt;2011-08-08&lt;/creationDate&gt;
 &lt;author&gt;Your name&lt;/author&gt;
 &lt;authorEmail&gt;Your e-mail address&lt;/authorEmail&gt;
 &lt;authorUrl&gt;Your web-site&lt;/authorUrl&gt;
 &lt;copyright&gt;Copyright Info&lt;/copyright&gt;
 &lt;license&gt;License Info&lt;/license&gt;
 &lt;!--  The version string is recorded in the components table --&gt;
 &lt;version&gt;1.00&lt;/version&gt;
 &lt;!-- The description is optional and defaults to the name --&gt;
 &lt;description&gt;CakePHP inside Joomla&lt;/description&gt;

 &lt;!-- Site Main File Copy Section --&gt;
 &lt;!-- Note the folder attribute: This attribute describes the folder
      to copy FROM in the package to install therefore files copied
      in this section are copied from /site/ in the package --&gt;
 &lt;files folder="site"&gt;
     &lt;filename&gt;cake.php&lt;/filename&gt;
     &lt;filename&gt;cake.html.php&lt;/filename&gt;
     &lt;filename&gt;index.php&lt;/filename&gt;
     &lt;filename&gt;README&lt;/filename&gt;
     &lt;folder&gt;app&lt;/folder&gt;
     &lt;folder&gt;cake&lt;/folder&gt;
     &lt;folder&gt;vendors&lt;/folder&gt;
     &lt;folder&gt;plugins&lt;/folder&gt;
 &lt;/files&gt;

 &lt;administration&gt;
  &lt;!-- Administration Menu Section --&gt;
  &lt;menu&gt;Cake&lt;/menu&gt;

  &lt;!-- Administration Main File Copy Section --&gt;
  &lt;files folder="admin"&gt;
      &lt;filename&gt;cake.php&lt;/filename&gt;
      &lt;filename&gt;index.html&lt;/filename&gt;
  &lt;/files&gt;

 &lt;/administration&gt;
&lt;/install&gt;</pre>
<p><strong>8.</strong> When you have saved <strong>install.xml</strong> -file, just pack it with the subdirectories &#8220;<strong>site</strong>&#8221; and &#8220;<strong>admin</strong>&#8221; to the zip-package &#8216;<strong>com_cake.zip</strong>&#8216;. This file can then be distributed and installed to Joomla simply with using Joomla&#8217;s Extension manager!<strong><br />
</strong></p>
<p>Article written by<br />
Jussi Laine</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2011/09/16/how-to-integrate-cakephp-to-joomla-1-6-as-a-component/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>If-fu or the Art of Using If Statements</title>
		<link>https://www.phz.fi/2009/11/11/if-fu-or-the-art-of-using-if-statements/</link>
		<comments>https://www.phz.fi/2009/11/11/if-fu-or-the-art-of-using-if-statements/#comments</comments>
		<pubDate>Wed, 11 Nov 2009 16:03:49 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Coding]]></category>

		<guid isPermaLink="false">http://pharazon.blog.com/?p=147</guid>
		<description><![CDATA[Writing complex if -statements without taking the readability aspect of the code in to the consideration will lead you quickly in despair. However, here are a few tips how you can write if -statements that are readable. The basic idea &#8230; <a href="https://www.phz.fi/2009/11/11/if-fu-or-the-art-of-using-if-statements/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Writing complex if -statements without taking the readability aspect of the code in to the consideration will lead you quickly in despair. However, here are a few tips how you can write if -statements that are readable.</p>
<p>The basic idea of writing easily readable if -statements is to use the State Machine -metaphore. The idea is that you model the function as a simple state transition diagram. There are two basic ways to write a simple state machine: the negative and positive.</p>
<p><strong>1. The Wrong Way &#8482;</strong></p>
<p>Here is a very common, but also a very uncomprehensible spaghetti -convention to write ifs like this:</p>
<pre>function wrongWayIf() {
    if (failureCondition1) {
        return false;
    } else if (failureCondition3) {
        if (failureCondition2) {
          return true;
        } else {
          //do something else
        }
    }
}</pre>
<p><strong>2. Positive Flat If State Machine</strong></p>
<p>A better idea is to use flat if-structure and a state machine -paradigm. An even better idea is to use pure Object Orientation <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . But if you still want to use ifs, try to omit the usage of elses and use the following two coding conventions. The idea of the positive If State Machine is to behave like a validator &#8211; you look look for conditions that causes the validation filter to fail. Finally, if you passed all filters, you return a success.</p>
<pre>function positiveIf() {
    if (failureCondition1) {
        return false;
    }
    if (failureCondition2) {
        return false;
    }
    //finally if nothing matches - success
    return true;
}</pre>
<p><strong>3. Negative Flat If State Machine</strong></p>
<p>The idea of the Negative If State Machine is to look for conditions where you can bail out immidiately. You should place the most common condition as the first, and then gradually iterate through more exotic conditions. This way you can linearize the decision tree, which would look like a uncomprehensible monster, if you&#8217;d use hierarchical if-else -statements. From the performance point of view you can also optimize your code this way by analyzing, which condition is the most common, and bail out first the most common cases. You can also leave the more performance heavy operations to the latter part of your if -case list.</p>
<pre>function negativeIf() {
    if (successCondition1) {
        //found a match, so bail out!
        return true;
    }
    if (successCondition2) {
        return true;
    }
    //finally fail if nothing matches - fail by default
    return false;
}</pre>
<p><strong>4. Complete State Machine Design Architecture</strong></p>
<p>Instead of writing if statements in the wrong way (deep if-else -spaghetti), model your state machine instead of a network of multiple simple state machines inside one class. You can write the state transitions even on a paper or as an UML chart, so you keep track what is going on:</p>
<pre>class MyStateMachine {
    function begin() {
        if (condition1) {
            positiveIf();
        }
        if (condition2) {
            negativeIf();
        }
       return end();
    }
//etc....
}</pre>
<p>The idea of an object is to hold a state, which should be changed only by the accessors. However, sometimes you need to make a complex stateful object and the state machine is the next best alternative.</p>
<p>I hope this helps you in getting ideas about how to write more comprehensive stateful code <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2009/11/11/if-fu-or-the-art-of-using-if-statements/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>The Zen of Raising Start-Up Funding</title>
		<link>https://www.phz.fi/2009/11/06/the-zen-of-raising-start-up-funding/</link>
		<comments>https://www.phz.fi/2009/11/06/the-zen-of-raising-start-up-funding/#comments</comments>
		<pubDate>Fri, 06 Nov 2009 09:06:16 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Finance]]></category>
		<category><![CDATA[Start-up]]></category>

		<guid isPermaLink="false">http://pharazon.blog.com/?p=134</guid>
		<description><![CDATA[I started recently to offer Financial Advisory consulting services for other Start-Ups due to my succesful track record in raising funding for my own companies. To leverage my combined experience in Financing and Start-Up business I&#8217;m looking for good business &#8230; <a href="https://www.phz.fi/2009/11/06/the-zen-of-raising-start-up-funding/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I started recently to offer Financial Advisory consulting services for other Start-Ups due to my succesful track record in raising funding for my own companies. To leverage my combined experience in Financing and Start-Up business I&#8217;m looking for good business ideas to mentor, advise and to invest in. The idea is that if I get personnally convinced of the Start-Up&#8217;s potential, I can increase the value of my Business Angel investment 10-fold by pitching in government grants, other business angels and VCs <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<p>However, before you should contact me, you should first reconsider at least these two aspects:</p>
<p><strong>1) Is your idea organized as a proper Growth Start-Up?</strong></p>
<p>I recently read the magnificient book by  Timmons, Spinelli and Zacharakis: &#8220;How to Raise Capital&#8221; that I suggest every entrepreneur wishing to join the Start-Up funding game should read first. I will post an article about screening of the growth start-up requirements later, but to mention one, you should never for example say in your pitch that it&#8217;s enough for you to get 1% of the world&#8217;s market to be profitable. The VC would think that he would rather invest in the market leader who gets 50% of the market. Secondly you shouldn&#8217;t bother to invent a better mousetrap (an incremental improvement, where corporations thrive in), but look for distruptive market innovations (as defined by Clayton Christensen) that the large corporations are unable to follow or cope with. Mark Twain once said:</p>
<p>&#8220;<em>The best swordsman in the world does not need to fear the second best swordsman in the world, but rather the man ignorant of swords but knowledgeable about gun powder</em>.&#8221;</p>
<p><strong>2) Minimizing the need for capital</strong></p>
<p>According to Timmons, Spinelli and Zacharakis, the difference between an entrepreneur and a corporate manager is that the latter one tries to maximize the amount of resources available for him, and yet thereafter to acquire a little extra to cope a downturn, while an entrepreneur seeks the opposite: to minimize the amount of resources required to run the business. From this truism, you can see many fundamental implications to the daily life of a Start-Up and why <a href="http://pharazon.blog.com/2009/08/13/start-ups-beware-50y-corporate-managers/">corporate managers will ruin your business</a>.</p>
<p>From the financing point of view (and my Financial Advisory) the first step is thus not to raise the maximum amount of capital that you can, but the opposite minimize the capital requirements in the first place. Raising the money for a good idea is not the problem &#8211; the world has today (even after the financial crisis) an excessive amount of capital floating around and unable to find better yields than you get from the bonds and the interest (2-5%). Thus if you can tell a believable investment story (please contact me for help in converting your business plan to a fundable Investment Story and the &#8220;Investor&#8221;-language), there is no limit to the capital you can raise. One famous example is the Swedish fashion Start-Up <a href="http://en.wikipedia.org/wiki/Boo.com">Boo.com</a> that raised 188M USD and burned it all in just 6 months by flying in First Class and living in 5-star hotels.</p>
<p>To launch a succesful Start-Up is like the Zen of Raising Finance &#8211; you should raise funding without getting any investments. Here are a few basic tricks that you should consider first.</p>
<p><strong>1. Staging</strong></p>
<p>The reason why the entrepreneurs should first consider substituting their intellect to the capital is that the longer you can manage keeping your business on the growth track without getting external funding the more your personal valuation will grow, or lower dilution. For example personally when I started <a href="http://www.CMAX.gg">CMAX.gg</a> I invested the bare minimum capital for shares (0,01 EUR per share), applied two government grants and got the hosting marketplace launched in 3 months (using Agile/Lean Product Development methods; <a href="http://www.extremeprogramming.org">Extreme Programming</a>). After proving that the idea is working, I managed to get the first extrenal investors to invest in at over 30x valuation to the price of my initial investment in just 9 months. Please calculate the ROI for my invested initial capital <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<p>The staging of the required investments is important from the entrepreneur&#8217;s personal valuation point of view, but also to practice the discipline of limited resources that gets you to think hard about the foundations of your business model. The more you think how you can run the business a few months longer without taking the bank loan, the lower capital cost you have and the higher valuation you can have, given that you progress in lowering the sunken costs, overall costs and the risk. In addition by executing the option to not purchase any resources you gain the flexibility to decommit quickly. You should keep all the time your multi-staged fund raising plan and the alternative options up-to-date for at least the next 3 years.</p>
<p><strong>2. Bootstrapping</strong></p>
<p>Bootstrapping is the process of resource minimization until the end of each stage or decision point (or raising the company up from the boot straps). A succesful entrepreneur Greg Gianforte (McAfee etc) stated:</p>
<p>&#8220;<em>a lot of entreprenreurs think they need money,&#8230; when actually they haven&#8217;t figured out the business equation</em>&#8220;.</p>
<p>To avoid the faith of Boo.com, you should check first that you are thinking like an entrepreneur and your team is thinking the same way. If you nevertheless do have a corporate manager (who can be very valuable if used properly, please read Geoffrey Moore: Crossing the Chasm) or other non-entrepreneurs such as an investor on your team or board, follow the advise by Art Spinner: i) Treat your board members as individuals ii) Always be honest to your directors iii) Found an audit &amp; compensation committee iv) Never found an executive committee.</p>
<p>Personally I managed for example to utilize my government subsidied Student Loan to raise the R&amp;D capital for <a href="http://www.CMAX.gg">CMAX.gg.</a> Secondly I managed to gain the experience how to run and fail an Extreme Programming R&amp;D -project on a university project, so I got the skill how to avoid the largest tar-pits for free. Also our 5-member coding team used the university premises for the first month until we managed to rent an office from the local business incubator.</p>
<p>At least in Finland you can easily access a wide range of government subsidies for hiring all kinds of consultants. Unfortunately the Start-Up logic is the opposite &#8211; you can&#8217;t outsource the skill how to run your own business. The art of using consultants is also Zen-like: when you need consultants you should not use them, but do the work yourself. Only thereafter you know the tasks for which you should have needed the consultants for, but often you realize that there is no need for the consultants anymore. If you use the consultant without first being knowledgeable about the topic yourself, you are just wasting your scarce financial resources for no gain, and demonstrating mental laziness from practicing the entrepreneurial bootstrapping.</p>
<p><strong>3. OPRs</strong></p>
<p>The OPR stands for &#8220;Other People&#8217;s Resouces&#8221; meaning all the different ways how you can barter, acquire, consult and loan all kinds of valuable resources for your business. If you bother to deeply analyze and understand the business you are in, this is probably the most significant method, how you can manage to lower your capital requirements footprint the most. The alternative is to pay huge amounts of money to consultants (and ignore their advice) as what the large corporate managers do daily to avoid the mental stress and political risk of taking responsibility. By being knowledgeable about your industry you can easily tune your business model so that you can substantially minimize your capital requirements. The basic MBA -way is to find different ways how you can negotiate longer payment times and shorter invoicing durations. The best alternative is actually to get your customers to pay in advance, for example by having an advance customer account like on the #CMAX.gg marketplace.</p>
<p>Find different ways how you can loan money without interest from your FFFs, clients and suppliers. Try to get advance payments, postponing of government obligations, talk and ask for bids from consultants (for free advise), utilize people at trade associations and the tax office to investigate on your business plan details, and every smartly think all possible ways how you can avoid from using your most scarce resource the cash. The most suprising method to raise capital for your business is to actually to pitch your business case to and ask your customers to pay for you of the product or service you are providing <img src='https://www.phz.fi/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . Consider always starting  your business as a consultancy to bring in early cash flow, filling out your CRM prospect list, learn the domain industry details in a safe manner, and investing your excess cash flow in product development. If you don&#8217;t have any skills that your prospective customers would value, it is unlikely that you can leverage them by developing products either.</p>
<p><strong>4. Lean Operations</strong></p>
<p>Amar Bhide has said:</p>
<p><em>&#8220;a startup is like zero inventory in a just-in-time system: it reveals hidden problems and forces the company to solve them.&#8221;</em></p>
<p>Invest your personal time in learning the Lean principles by your heart. After knowing of the Lean, Kaizen, Kanban and all the related methods, study next the Six Sigma, which is the second sub-school of the TQM and allows you to find even more fundamental ways how to avoid waste and minimize your capital requirements in every operation from sales funnel to product development.</p>
<p><strong>Summary</strong></p>
<p>In another words, instead of first sending your business plan to the venture capital, think about the ways how you tune your business model so that you don&#8217;t need any capital in the first place, and the business model is generating large amounts of free cash flow when it grows. If your business is draining capital as it grows, you should not probably be growing.</p>
<p>The 99% of the world can afford mental laziness and avoid thinking very hard how they can manage their daily lives. The entrepreneurs however, do not have this luxury, but require the mindset of trying to always minimize the capital commitments for lower dilution and lower personal risk. The main tools for a succesful entrepreneur are the Staging of investments, Bootstrapping or minimizing the capital footprint, the Other People&#8217;s Resources and the Lean operations. Instead of hiring a consultant to do the thinking, you should invest your own brain capacity to do a small excersice in Excel and trying to simplify your business idea in a formula that tells yourself and the investors a success story.<a href="http://www.hut.fi"></a></p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2009/11/06/the-zen-of-raising-start-up-funding/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Finland is Missing the Bowling Alley or How to Find the First Pin</title>
		<link>https://www.phz.fi/2009/10/28/finland-is-missing-the-bowling-alley-or-how-to-find-the-first-pin/</link>
		<comments>https://www.phz.fi/2009/10/28/finland-is-missing-the-bowling-alley-or-how-to-find-the-first-pin/#comments</comments>
		<pubDate>Wed, 28 Oct 2009 09:23:28 +0000</pubDate>
		<dc:creator>Hätinen Antti</dc:creator>
				<category><![CDATA[Marketing]]></category>
		<category><![CDATA[Start-up]]></category>
		<category><![CDATA[Strategy]]></category>

		<guid isPermaLink="false">http://pharazon.blog.com/?p=128</guid>
		<description><![CDATA[I read Antti Hannula&#8217;s (Tikitagi) blog on Mårten Mickos&#8217;s comments on &#8220;How to build a Global Software Company&#8221; and agreed on him on all of the points. The challenge for the Finnish and other periphery market technology companies is the &#8230; <a href="https://www.phz.fi/2009/10/28/finland-is-missing-the-bowling-alley-or-how-to-find-the-first-pin/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I read Antti Hannula&#8217;s (Tikitagi) blog on Mårten Mickos&#8217;s comments on &#8220;<a href="http://gasellit.wordpress.com/2009/10/19/marten-mickos-how-to-build-a-global-software-company-in-finland/">How to build a Global Software Company</a>&#8221; and agreed on him on all of the points. The challenge for the Finnish and other periphery market technology companies is the smallness &#8211; Finland is a pilot market, which means that it is easy to launch services, get a relatively high market share, but which is insuffucient to compensate for the R&amp;D investment made. Secondly the Finns are notoriously bad in sales and marketing activities, or the national culture undervalues the importance of these activities compared to hard-core engineering and hard labour. Here is a summary of some points by Mårten Mickos, the founder of <a href="http://www.mysql.com">MySQL</a>:</p>
<p>1. Finland is too small and expensive<br />
- but maybe the engineers are the world&#8217;s cheapest ones, you get paid more even in China for high skill work<br />
- Geoffrey Moore stated in the &#8220;Inside the Tornado&#8221; about the Bowling Alley Strategy that you should plan the how you turn over the Pins by the Word of Mouth &#8211; relations between the market segments. The first and worst property of the Finnish market is that nobody else in Europe understands the Finnish language (except for the Estonians) and thus the natural WoM-Pin Bowling Alley stops at the border. On the English -speaking world you can easily map the Alley, while by starting from Finland the Alley might not exist at all.</p>
<p>2. Finland is not good at producing software globally<br />
- too many start-ups and SMEs investing (relatively) huge amounts on R&amp;D create their softwares with Finnish-only interfaces &#8211; FAIL</p>
<p>3. Finns are slow<br />
- the dominant idea is to risk-awersily get the products to the markets by starting small, and growing organically. Instead you should think big and design the products to be globally utilizable from the day one onwards.</p>
<p>4. Finns believe too much in subsidies and R&amp;D grants over sales to customers<br />
- in the other countries the governments don&#8217;t issue subsidies, but grant tax exempts for R&amp;D activities and investments. Giving tax exempts is, however, against the core values of the Welfare State &#8211; everybody needs to pay a lot of taxes, unless of course, you don&#8217;t have any income (and you are applicable for grants and allowances).</p>
<p>5. Lack of will to fight &#8211; life is too easy<br />
- why to bother to work hard if the social security system covers your livelihood, if you don&#8217;t work, and the high taxes ensure that you won&#8217;t get rewarded of your above-average gains</p>
<p>6. Unjustified belief in being on the leading edge (the country of Nokia etc.) and &#8220;test laboratory&#8221;<br />
- too bad that you can&#8217;t make much money out of a test lab, since there are no customers</p>
<p>I have to admit that I have personally fallen in all of these traps with <a href="http://www.cmax.gg" target="_blank">#CMAX.g</a>g hosting marketplace. First of all we gained upto 70% marketshare on the Finnish online gaming server market in a relatively short time span and thought to expand to Germany to capitalize on our lucrative innovation. However, mostly due to <a href="http://pharazon.blog.com/2009/08/13/start-ups-beware-50y-corporate-managers/">bad execution</a> and wrong people recruited for the management, not the business itself, the newly appointed Board managed to burn all the freshly raised capital just in 5 months in direct contradiction to the Business Plan, and the project was dead. This proves the <strong>lack of the bowling alley</strong> &#8211; nobody in Germany had heard of the market leader in Finland, or we would have known in advance about the market adaption challenges that faced us outside the borders. Secondly it seems that Finland is also <strong>missing the bowlers</strong>,<strong> </strong>who would have any experience on how to see where the pins are in the first place or how to hit them in the right place to initiate the domino-effect.</p>
<p>Producing the global software is also a more substantial challenge for the Finns than for example German or US companies, since due to the small market size we are missing the momentum to finance the functional expansion to adapt to the internationalization challenges. For example all international softwares have to support for example character encoding, locales, translations, currency conversions, tax regulations, legal differences and you can&#8217;t even host your website from Finland to Central Europe. The US companies have none of these extra challenges and have the access for larger amounts of customers and equity funding on their home markets. The EU tries to create a common internal market, but in many fields such as taxation it&#8217;s still a distant dream, and the fragmented language barriers won&#8217;t disappear by any Directive of the EU Commission.</p>
<p>Third, according to our initial plan we could have been able to start the internationalizations efforts already in 2005, but we had pitch for 2 years for different kind of small grants of a few 10k EUR&#8217;s before we finally got a VC convinced for a barely sufficient equity stake. We were already almost passed the opportunity window before the funding had reached the point that you could potentially have even a slightest chance to get the international business up and running, in theory. In the US we would have raised 10x the funding several years earlier and been able to provide adequate returns from the home market alone.</p>
<p>The fourth and fifth problem mentioned above are related to the grants, corporate managers retiring to the Start-Up scene and the too easy life provided by the Wellfare State. It might be also a good strategy to give money to the start-ups in small quantities, since otherwise it seems that the money is also quickly spent, especially if you let Corporate Managers to run a Start-Up (who have accustomized themselves to not care about the costs and who don&#8217;t have any substance or skill themselves, but outsource everything at a large corporation&#8217;s cost level). You can easily spend tens of millions of euros without ever seeing any return, and thus at least from the Government&#8217;s point of view it sounds like a good idea. The Start-Up business requires a different kind of management, or entrepreneurship, not management. Nevertheless the entrepreneurs, who have purified themselves from the corporate management culture, are still left with the Finnish national cultural backload of not trying hard enough, or not going as far what it takes to be truely succesful. This is instantiated in two phenomenon: the lack of sales effort and the tendency to ignore the necessity to do the hardest part of the work. The corporate managers are a better in the first part, they are accustomized to sell to B2B clients. However, the entrepreneurs are typically technology enthusiastics themselves and are anyway at least in their deep soul fearful from for example making cold calls or thinking about to be innovative in doing viral marketing (to cut costs). However, the lazy (or seemingly hectic) corporate life have accustomized the managers to 1) specialize and 2) to delegate, meaning that they offer little substance for the challenges of the daily life of a small company since you 1) need to know quite a lot about every aspect of running of a company, you just can&#8217;t make it if you are not willing to learn. The corporate managers are usually not willing, and 2) you don&#8217;t have the financial resources, departments and skill pool accessible for a large corporation, so you need to do often everything from accounting and cleaning to sales brochures by yourself in the start. Anyway, if you don&#8217;t know how to do accounting yourself, you don&#8217;t know how to outsource it either. Your outsourcing partner (accounting company) is mostly interested in how to charge you extravagant amounts especially if you don&#8217;t precisely know how the accounting should be run cost-effectively. There is simply no way out of the fact that to run a Start-Up you need to learn and fast.</p>
<p>Finally the Finnish national idea of being the &#8220;mobile technology test laboratory&#8221; has always been a strange idea for me, since I can&#8217;t see the money. Typically the customer requirements from country to country vary wildly, and by designing a successful service for the Finns doesn&#8217;t mean that it would be successful in Sweden, Germany, or in China, without some substantial modifications and local adaptions. To develop a global software, you need to develop the product to adapt by minimum two in different countries, to draw the generic design that is common to these markets. Otherwise you are building a local software company that can&#8217;t every grow to sizes that would justify the trouble in running a growth oriented Start-up. A good idea, however, is to start a local company on a large domestic market, like the US.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phz.fi/2009/10/28/finland-is-missing-the-bowling-alley-or-how-to-find-the-first-pin/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

