Tuesday, December 7, 2010

Missing Base SDK in existing projects when upgrading to Xcode 3.2.x

This article describes how to solve the problem of Missing Base SDK. This problem happend to me after updating to Xcode 3.2.5. While researching I saw that few people had this problem also with Xcode 3.2.3 and 3.2.4. This fix should work for any of the above versions.

Downpoint for Xcode 3.2.5 is that only IOS 4.2 is supported furthermore. I will soon describe in another article how to set Xcode 3.2.4 and 3.2.5 in parallel up on your system to use older SDKs as well.

To perform the steps described below open the according project in Xcode.

1. Change the SDK of the project


1. From the Xcode menu bar, select Project > Edit Project Settings
2. Select the "Build tab"
3. Select "All Configurations" under "Configurations"
4. Under Architecture > Base SDK, choose depending on what is available the according iOS SDK (for Xcode 3.2.5 iOS 4.2)

2. Change the SDK of the target


1. Make sure the right target is selected. In the menu bar top of the screen, select Project > Active Target > "DesiredTarget"
2. From the menu, select Project > Edit Active Target "DesiredTarget"
3. Select the "Build" tab
4. Select "All Configurations" under "Configurations"
5. Under Architecture > Base SDK, choose depending on what is available the according iOS SDK (for Xcode 3.2.5 iOS 4.2)

3. Support older iOS versions


If you want to support previous iOS versions, when you perform the steps under 1. and 2. in the same windows, under Deployment > iPhone OS Deployment Target, select the lowest version you want to support. Support for iPhone OS 2.x versions is deprecated for apps through the app store.

Friday, December 3, 2010

Xcode Linking Problem: symbol(s) not found

The discussed problem


For my recent iPhone Xcode Cocoa project I setup mogenerator to autamtically get the classes created that match an entity in the xcdatamodel. When I later tried to use those objects in my ViewControllers I couldn't get the application build.

The error said something a long the lines:

Undefined symbols:
   "_OBJC_CLASS_$_Recipes", referenced from:
       objc-class-ref-to-Recipes in RootViewController.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

Whereas "Recipes" stands for one of my entities in my CoreData datamodel.

The fix of the linking error

It took me hours to figure this one out. In XCode if you click on your project root in the Groups & Files View (normally on the left of the XCode window), you can see (on the right hand side of the XCode window) the list of all files in the project. As you can see below in the picture.

Make sure that in the last column of the list for each .m file (especially for the once that caused the error) the box is checked. If the box is checked the file is included in the default target that gets build.

The reasons for linking errors

If you never change anything in the target files and/or related build configuration, all your new classes should be included in the build of the target automatically.

The file that caused my error was generated with the mogenerator. The generator creates a subfolder in your Xcode project and it seems to me like all the files in there are not automatically added to the target.

Monday, November 22, 2010

Best Practice Localization/Internationalization Xcode iPhone project - NIB files

This article describes how to setup localization on your NIB files (.xib files).

I wrote another article about how to setup localization for your Strings in the class files (.m files). I recommend reading the other article to get a better understanding of the whole localization process of iPhone apps with Xcode.

Create localized NIB files

First you should bring the according file into a status that can be called complete. You can afterwards still change the file but it makes sense to edit the other language views only once aat the end.

To create the localizations perform the following steps:
1. Right-click on the according .xib file in the Resource folder and select "Get Info".
2. In the window under "General" click "Make file Localizable".
3. Then again under "General" repeat "Add Localization" for all languages you want to support.

The result in the Groups & Files view of Xcode should look like below.


Change the text of labels, buttons etc. for the according language of the .NIB file


Now that you have all the language versions under the NIB file, you can open each separately and change button and label text for the language.
Now Build and Run you App on the iPhone simulator. When you change the language of the iPhone to one of the languages provided in your app the buttons and labels will show the right language.

Troubleshooting: I changed the language in the iPhone but the old NIB file content is displayed


Try to:

1. Clean the Target.
2. Remove the app from the iPhone emulator. (Hold left mouse button down on any app icon until the icons wiggle. Click x for delete. Click iPhone Home button.)
3. Build and Run again.

Sometimes deleting the build folder could also help.

Best Practice Localization/Internationalization Xcode iPhone project - Localizable.strings

This article is about how to create a Localizable.strings file for an Xcode iPhone project, in order to provide translations for Strings that were used within the class files. Also common problems that can accure when dealing with internationalization of an xcode project are discussed.

I also wrote an article about how to add localization for .xib files (NIB-files).

1. Create Localizable.strings files for all supported languages


1. In any xcode iPhone project right-click on the Resources folder and select "Add->New File...". 2. Under the available iPhone templates is no .strings file available so we select "Mac OS X -> Resource -> Strings File" and click Next.
3. Name the file Localizable.strings.
4. Right-click on the file in the Resource folder and select "Get Info".
5. Under "General" click "Make file Localizable".
6. Then again under "General" repeat "Add Localization" for all languages you want to support.

The result for the localized Localizable.strings file in the Groups & Files view of Xcode should look like this:



2. Change plain Strings in class files to the NSLocalizedString macro


Everywhere in the .m files where you are using NSStrings (e.g. @"text";) you need to replace the part with NSLocalizedString(@"textKey", @"comment");.

If you don't want to fill in a comment with your key leave the according filed nil.
The textKey will later be looked up in the according strings file for the language of the iPhone user and replaced with the translated text.

Example entry in a Localizable.strings file:
/* comment */
"exitLabel" = "Exit";

3. Automatically extracting the key's into the Localizable.strings files


Often you can find on the Web that you should create an en.lproj, de.lproj, fr.lproj folder etc. to hold the Localizable.strings files. When we perform our first step Xcode generates English.lproj, German.lproj, French.lproj folders and places the Localizable.strings files in.

That is for newer versions of Xcode, in my case Xcode 3.2.4.

To extract all the keys used in your .m-files run the below command that will write the keys with the according command in the according Localizable.strings files. You have to run the command in the command line of your console and you have to be in the according xcode project.

genstrings -o English.lproj *.m
genstrings -o German.lproj *.m
genstrings -o French.lproj *.m

Important! This command will override the old entries. Be aware and save the old file.

Now you are good to go and when you Build and Run your app the changes should be applied.

Problem: The Localizable.strings file contains inverted question marks


This can happen when the according file is not in UTF-16. You can solve this problem in Xcode though. Right-click on the file and select "Get Info". There select File Encoding UTF-16, when a popup asks you: "Do you want to convert the file to the new encoding or reinterpret it using the new encoding?" Select "Convert". That's it.

Note: Sometimes I have to run the genstrings commands again in order that the encoding works, after I changed it.

Problem: NSLocalizedString only returns the key


Try to:

1. Make sure you got the case right for each character in the key.
2. Clean the Target.
3. Remove the app from the iPhone emulator. (Hold left mouse button down on any app icon until the icons wiggle. Click x for delete. Click iPhone Home button.)
4. Build and Run again.

Sometimes deleting the build folder could also help.

Wednesday, November 17, 2010

Hattrick Online Soccer Manager Match Viewer App for iPhone and Android

Currently I am working on an iPhone App for Hattrick. It will be a match viewer for Hattrick.

The project name is SmartViewer and this is the website for the Hattrick Match Viewer Mobile App. Here you can get the updates about the release dates and available versions.

The first free version will be released early next year (2011).
Shortly after that an Android App will be released.

Some of the features are:
  • direct link to the users match
  • via favorites also a direct link to matches of other users
  • the user is able to create conferences to follow several games in parallel
  • during the match information about crowd, weather, goals, cards, substitutions, injuries, special events and ball possession will be provided
  • after the game in addition the ratings and maybe some special stats, like hatstats, will be provided

Monday, November 15, 2010

Install and run SVN server on Linux with browser based administration GUI

This article should describe how to install and setup a SVN server on Linux Debian Lenny. The instructions may also work for other Linux distributions like Ubuntu, Fedora, RedHat and Open Suse.

1. Install the packages necessary for the SVN server

apt-get install php-pear libapache2-svn subversion
pear install -f -o VersionControl_SVN
Setup the repository root directory.
mkdir -p /var/lib/svn/repos
touch /var/lib/svn/htpasswd
touch /var/lib/svn/accessfile
chown -R www-data:www-data /var/lib/svn

2. Configurate the dav_svn module in Apache2

nano /etc/apache2/mods-available/dav_svn.conf
Add the following lines or make sure they are not commented (no leading #). There might be more content between the location tags but below are the values that have to be there with those values.
<Location /svn>
  DAV svn
  SVNParentPath /var/lib/svn/repos
  AuthType Basic
  AuthName “Subversion Repository”
  AuthUserFile /var/lib/svn/htpasswd
  # SSLRequireSSL
  AuthzSVNAccessFile /var/lib/svn/accessfile

  Require valid-user

</Location>

3. Download SVNManager and setup


Change into the folder for the apache server pages. By default that is /var/www.
cd /var/www
Create the folder for the svnmanager and download the svnmanager file there. Check on svnmanager.org for the current version and right click on the download link and copy the link address.
mkdir svnmanger
wget http://prdownloads.sourceforge.net/svnmanager/svnmanager-1.08.tar.gz
After the download is done extract the archive and copy the files into the "nice folder".
tar xvfz svnmanager-1.xx.tar.gz
cp -R svnmanager-1.xx/* /var/www/svnmanager
Enable the default linux config file.
cd /var/www/svnmanager
cp config.php.linux config.php
Enable the dav_svn module for apache2.
a2enmod dav_svn
invoke-rc.d apache2 restart

4. Edit the config.php

nano config.php
Change the lines to following values.
<?php
//Shell command’s
$htpassword_cmd = “/usr/bin/htpasswd”;
$svn_cmd = “/usr/bin/svn”;
$svnadmin_cmd = “/usr/bin/svnadmin”;

//Subversion
$svn_repos_loc = “/var/lib/svn/repos”;
$svn_passwd_file= “/var/lib/svn/htpasswd”;
$svn_access_file= “/var/lib/svn/accessfile”;

//SMTP Server
$smtp_server = “localhost”;

//Database
$dsn =”mysqli://svnmanager:password@localhost/svnmanager”;

//Administrator account
$admin_name=”admin”;
$admin_temp_password=”admin”;

5. Create Databases and User Account


If you have phpmyadmin installed (e.g. via ttp://your-ip/phpmyadmin) you can create a new database there called svnmanager. Also create a user (e.g. svnuser) with password and grant select, insert, update, delete, create, drop, alter rights.

If you don't have phpmyadmin run following commands:
mysql -u root -p -e”CREATE DATABASE svnmanager; \
GRANT select, insert, update, delete, create, drop, alter on svnmanager.* to ’svnmanager’@'localhost’ identified by ‘password’; \
FLUSH PRIVILEGES;

6. Create admin user in SVN Manager and create repository and grant access


With the Administrator account configured in the config.php login to your svnmanager at http://your-IP/svnmanager. Create a new user with administrator rights there. Because for security reasons you can not use the initial administrator account to create repositories and users.

So log out and login with the new admin user and setup repositories users and privileges.
Repositories created via SVNManager look like:
http://your-ip/svn/repository-name

7. Troubleshooting for Linux Debian Lenny


For Linux Debian Lenny following bug is known. A locale setting problem can occur if en_US.UTF-8 is not used. This can be fixed with the following steps.
apt-get install locales
export LC_ALL=en_US.UTF-8
dpkg-reconfigure locales (run the command and select en_US.UTF-8 as default)
reboot

Wednesday, November 10, 2010

Problem opening .xib file(NIB file) with xCode 3.x.x

The .xib or NIB files in a iOS project are supposed to open with the Interface Builder.
-> Although the Interface Builder files end with the .xib extension, often Cocoa programmers still refer to them by their old name, NIBs.

After installing Xcode 3.2.4 and the iOS SDK 4.1 the .xib files in my projects didn't open after double click.

When right clicking on the files in Xcode it only showed under "Open As" the options Plain Text File or Source Code File. I searched the Web and people often recommend to reinstall Xcode. That totally did not work for me.

The solution


First I checked if the Interface Builder was installed on my Mac OS. I used the Spotlight (Command+Space opens the search field) and searched for Interface Builder. Surprisingly the Builder was installed on the System.

If the Interface Builder is installed you have to connect the NIB files with it. Use Finder to navigate to one of your NIB files in any Xcode project. Then right-click and use File > Open With and select Interface Builder. Since I did that one time I could always open any .xib file directly in XCode project view with double click.

Tuesday, November 9, 2010

Error in Hibernate: unexpected token: LIMIT near line ...

Why do I get the "unexpected token: LIMIT..." error?


Hibernate doesn't support the LIMIT token in a SELECT statement. Whereas in a Java SQL PreparedStatement to define a limit on the result set you can use the LIMIT command in the SELECT query.
SELECT * FROM person_table as person WHERE last_name = "Miller" LIMIT 0,20;
If you use Hibernate instead this command wouldn't work. You would get an error looking like:
"unexpected token: LIMIT near line 1 column..."

How to limit results for paging in a Hibernate query?


The proper way in Hibernate select queries would be to use:
query.setFirstResult(0);
query.setMaxResults(20);

Example on how to use paging in Hibernate HQL select query?


Below an example on how to execute a SELECT query with a limit on the returned result set in Hibernate. Especially if you want to use pagination through the results this example how to pass in a page size and page number and get the according results from the table.
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

import java.util.List;
.
.
.

public List<Person> getPersonsByLastName(String lastName, int pageSize, int pageNum) throws Exception
{
 //unless you need only certain columns you don't need to add the SELECT part in the select query (HQL query) in Hibernate 
 String sqlQuery = "FROM Person as person where person.lastName=:lastName";
  
 Query query = workspace.getReadOnlyDbQuery(sqlQuery);

 // Pass all the parameters
 query.setParameter("lastName", lastName);
 query.setFirstResult(getStartIndex(pageSize, pageNum));
 query.setMaxResults(pageSize);
  
 List<Person> result = query.list();
 if (result != null && result.size() > 0) {
  return result;
 } else {
  return null;
 }
}

public Query getReadOnlyDbQuery(String queryString) throws Exception
{
 Session session = getReadOnlyDbSession();
 Query query = session.createQuery(queryString);
 query.setTimeout(5);
 return query;
}

//calculates the start index for pagination
private int getStartIndex(int pageSize, int pageNum) {
 if (pageNum < 1)
  return 0;

 return (pageNum - 1) * pageSize;
}

Thursday, November 4, 2010

How to enabled mod_rewrite on Apache2.2 and Linux Debian 5 Lenny

Below a little tutorial with an example on how to enable apache mod_rewrite to be able to use htaccess, that is for example necessary to use pretty permalinks in WordPress and Joomla, with URL rewriting. The is no extra download necessary if you have Apache 2.2 already installed and running.


There are two easy steps to activate, install or enable mod_rewrite for Apache 2.2 Web Server. Go in the Command line of your linux. It doesn't matter where you are in the folder structure, but you should either be the root or put sudo infront of the commands, like below to switch to the root user.

1. Type below command and hit enter

sudo a2enmod rewrite
A successful response should be:
Enabling module rewrite.
Run '/etc/init.d/apache2 restart' to activate new configuration!


2. Restart Apache2.2

/etc/init.d/apache2 restart
Again, use sudo in front of the command, if you not logged in as root user.
You could also use:
/etc/init.d/apache2 stop
/etc/init.d/apache2 start
If you have problems in Linux RedHat you might want to try:
/etc/init.d/httpd restart

Problem: /etc/init.d/apache2 command not found


Another command that could work instead is:
sudo service apache2 restart
Another reason for this error message is that you might not have installed apache2 properly. In this case it helps to delete the old installation and run a clean install via.
sudo apt-get install apache2


Problem: /etc/init.d/apache2 permission denied



If you are not the root user run:
sudo /etc/init.d/apache2 restart
The instructions might also work for other Debian versions and other Linux destributions like Open Suse Linux, Ubuntu, RedHat etc. Let me know if you have problems with the instructions.

Monday, November 1, 2010

JMeter - How to reset the listeners

Especially when writing a new load test you work a lot with the jmeter listeners. After a while you don't want old requests to show up in the listener log anymore. Otherwise you have to scroll forever.

My first attempt to empty the listener was to right click on one. But there is no option to reset the listeners (e.g. Summary Report or View Result Tree).

Then for a while I always was just saving my JMeter load test and reopening it to get rid of the old logs in the listener and I was ok with that. Since a co worker asked me about it, I thought there must be a better way and after some investigation I found the answer to how to empty the listener.

Either in the "Run menu", select "Clear" when the according listener is selected. Or you select Clear all if you want to reset or listener logs.

The according shortcuts are Control+Shift+E and Control+E.

Saturday, October 30, 2010

A free and customizable full width blogger template

Here my first design of a full width blogger template. The background that shows up on the sides of the template is blue and the content area is white. The layout of the blogspot theme has the articles on the left and the space for widgets on the right side.

Also most of the text and the links are dark blue or black as you can see in the screenshot. The picture in the header doesn't come with the template, but you are able to upload your own picture and display the blog title and a description in the header area as displayed further below.

Terms of Use

Using this blogspot theme is free of charge. Anybody can use or change the template as long as the copyright link stays in the footer of the template.

Download the template

Here is the download link of the full width free customizable blogger template:

How to install the blogger template and backup your old template?

1. Go to blogger.com when you are logged in to your blogger account.
2. In the Dashboard under manage blogs find your according blog (in case you have more than one).
3. Click on "Design"
4. Click on "Edit HTML"
5. Click on "Download Full Template" to backup your current template.
6. Click on "Choose File" to select the full-width-template.xml file on your hard drive.
7. Click on "Upload" to upload the template.
8. In case you have widgets on the blog that are missing in this template a Warning will show up. Click "Keep Widgets" to add the missing widgets.

And you are finished.

Upload a header image and add blog title and description to the heading.

To upload a header image to the template you can repeat steps 1. to 3. above and then click on "Page Elements".

Here you see the layout of the template. Click at the header on "Edit". A window pops up that lets you upload an image. If you have already an image there and you want to changes, click on "Remove Image".

If your image contains all the Text you want in the header area select under Placement "Instead of title and description". If you want to add a title and or description select "Behind title and description". Then make sure you have enter the text you want to show up later in the header area above in the "Title" and "Description" field.

Customize text, link and background colors of the full width blogger template

Repeat step 1. to 4. and scroll down in the "Edit Template" area to the "Variable definitions" section. Here you can change the value parameter of variables like the background color or links. After you have saved the changes you can review them on the blog.

Thursday, October 21, 2010

Configuring Apache2.2 with Tomcat 6.0.x on Linux with mod_proxy_ajp

This article should describe how to use the HTTPD Server Apache 2.2 to handle all request on port 80 and in certain cases (depending on the requested url) pass on the request to the Apache Tomcat 6 Server (6.0.x). So your Apache can handle requests for your static pages and for example php projects and your tomcat handles java web applications in for example .war-files.

How to let Apache 2 forward requests for different URLs to different folders is described in a former article.

These setting were used and tested in Linux Debian 5 Lenny. They should also work in other distributions like RedHad, Suse and Ubuntu, as well as Windows and Mac OS systems.

Make sure mod_proxy_ajp is installed

Look into /etc/apache2/mods-available for proxy.load and proxy_ajp.load to make sure the according mods are available.
Look into /etc/apache2/mods-enabled for proxy.load and proxy_ajp.load to make sure both mods are enabled.

From Apache2.2 this should normally be all set already, if not read here on how to enable mod_proxy_ajp.

Apache2 configuration

As described in a former post I use virtual host to handle requests for different URLs and forward them to certain folders in the /var/www folder. To forward requests to a certain URL to tomcat can also be done by creating a virtual host.

ProxyPass and ProxyPassReverse are classic reverse proxy directives used to forward the stream to another location. ajp://… is the AJP connector location (your tomcat’s server host/port)

The changes have to be done in the sites-enabled/000-default file in the apache2 home folder. Use for example nano or vim to edit the file and at below block.
nano /etc/apache2/sites-enabled/000-default
#Virtual Host setup
<VirtualHost *:80>
   ServerName yourapp.com
   ServerAlias *.yourapp.com
   DocumentRoot "/usr/local/tomcat/webapps/yourapp"
   DirectoryIndex index.html
   <Proxy *>
      AddDefaultCharset Off
      Order deny,allow
      Allow from All
   </Proxy>
   ProxyPass / ajp://localhost:8009/
   ProxyPassReverse / ajp://localhost:8009/
</VirtualHost>

Tomcat configuration

In Tomcat 6 we have to do some changes as well. All changes are done in the server.xml file.
nano /usr/local/tomcat/conf/server.xml
The first line might already be in your file. Please check this and in case only add the missing parameters, like enableLookups="false". This line will enable the AJP connections to the 8009 port of your tomcat 6 server.
<Connector enableLookups="false" port="8009" protocol="AJP/1.3" redirectPort="8443" />
Add this inbetween the tags:

<Host name="yourapp.com" debug="0" appBase="/usr/local/tomcat/webapps"
 unpackWARs="true" autoDeploy="true">
   <Alias>wiki</Alias>
   <Context path= "" docBase="/usr/local/tomcat/webapps/yourapp" debug=”1″/>
   <Valve className=”org.apache.catalina.valves.AccessLogValve”
    directory=”logs” prefix=”home_access_log.” suffix=”.txt”
    pattern=”common” resolveHosts=”false”/>
</Host>

Now if you point your domain(yourdomain.com) to the IP of the server Apache should do the rest and Tomcat should serve you the web app.

Restart Apache and Tomcat

Restart Apache:
/etc/init.d/apache2 restart
Restart Tomcat:
/etc/init.d/tomcat restart

Tuesday, October 19, 2010

Symfony - extract locale from url or user (sf_culture)

If your symfony supports different cultures like en, fr, ru, de, uk etc. you can set in the routing.yml the prefix_path: /:sf_culture/module

To get the value from the URL

In an action you can use:
$locale = $request->getParameter('sf_culture');
If you are not in an action, maybe you want to put this in a helper class or Filter.
$locale = $this->getContext()->getRequest()->getParameter('sf_culture');

To get this value from the user object instead

In an action you can use:
$locale = $this->getUser()->getCulture();
Outside of an action use:
$locale = $this->getContext()->getUser()->getCulture();
Furthermore, here are some more explanations about the culture support from Symfony explained with an example.

Friday, October 15, 2010

JavaScript find form fields type=file and remove from form if empty when submit

 Following situation. You have a form that should upload files to your server. The user should be able to set a file in the input field file or not. If the file is not set you don't want the form to send the parameter.

This text describes how you can check these file input fields with JavaScript and in case remove them from the form before it gets send.

The form HTML code:
<form action="myurl.com" enctype="multipart/form-data" id="form1" method="post">
<input name="text1" type="text" />
   <input name="text2" type="text" />

   <input name="field1" type="file" />


   <input name="field2" type="file" />
   <input name="field3" type="file" />
   <input onclick="checkFileFields();" type="submit" value="Execute" />
</form>

Put this JavaScript funtion in between the <head></head>-tags:
<script language="javascript">
function checkFileFields()
{
   //get the form with the id form1
   var form = document.getElementById("form1");
   //get all elements in the form, e.g. the input fields
   var formElements = form.elements;
   //initialize an array that stores all the elements that later get deleted, don't delete when you find the element, otherwise you change the formElements array size and miss another field
   var toDeleteElements = [];
   
   for (x in formElements)
   {
      //each element that has type='file' and value is 0 is what we want to delete later
      if(formElements[x].type=='file'&&formElements[x].value.length==0)
      {
         toDeleteElements.push(formElements[x]);
 
      }
   }
   
   for (x in toDeleteElements)
   {
      //remove element from its own parent
      toDeleteElements[x].parentNode.removeChild(toDeleteElements[x]);
   }
}
</script>

Thursday, October 14, 2010

Setting up a FTP server on Linux Debian 5

A simple to use and very popular FTP server is proftpd basic. How to install this FTP server, create an user that has only access via FTP and give him access to a desired directory like /var/www is the context of this article. Although this might work for any kind of Linux like RedHead, Ubuntu, SuSe etc. the instructions are tested for the Linux distribution Debian 5 Lenny.

Install proftpd

Install the proftpd package

To install it on your Debian 5 server the following command from the command line:
sodu apt-get install proftpd-basic

Error: Couldn't find package

If you run into this error message:
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Couldn't find package proftpd-basic
Make sure your sources.list file contains all the necessary source locations.
The sources.list can be found in Debian 5 at /etc/apt/sources.list.

Here are the entries my sources.list contains:
## main & security repositories
deb http://ftp.us.debian.org/debian/ lenny main contrib non-free
deb-src http://ftp.us.debian.org/debian/ lenny main contrib non-free
deb http://security.debian.org/ lenny/updates main contrib non-free
deb-src http://security.debian.org/ lenny/updates main contrib non-free

Here is a great source list creater tool to fix or update your sources.list in case you don't have the original one anymore.

Important!
After saving any changes to the sources.list run the:
apt-get update
command to update the package list. Now run the install command again.

Run as standalone

When the proftpd installation started successfully you get asked if you want to run the ftp-server under inetd or standalone. Selecting standalone that seems to cause the fewest problem to get the FTP server actually running. You can change this setting later in the proftpd.conf file mentioned below under ServerType.



Create a virtual FTP user and give him access to /var/www

Create a folder for the virtual user

sudo mkdir -p /home/webadmin

Set proftpd user as owner of folder

The proftpd's default user is named proftpd if you didn't changed. Make him the owner of the folder:
sudo chown -R proftpd:nogroup /home/webadmin

Get user proftpd's uid and pid

sudo grep ftp /etc/passwd
Looks like:
proftpd:x:106:65534::/var/run/proftpd:/bin/false

Create the virtual FTP user

sudo ftpasswd --passwd --name=webadmin --uid=106 --gid=65534 --home=/home/webadmin --shell=/bin/false --file=/etc/proftpd/passwd
Response in the CLI:
ftpasswd: using alternate file: /etc/proftpd/passwd
ftpasswd: creating passwd entry for user webadmin

ftpasswd: /bin/false is not among the valid system shells.  Use of
ftpasswd: "RequireValidShell off" may be required, and the PAM
ftpasswd: module configuration may need to be adjusted.

Password:
Re-type password:
After the password was re-typed it says:
ftpasswd: entry created

Install PAM

Then I installed PAM but I am not sure if this is really necessary:
apt-get install libpam-pwdfile

Setup the config file

Now we have to change the proftpd config file:
nano /etc/proftpd/proftpd.conf
Following stuff should be in the file:
DefaultRoot                  ~
RequireValidShell            off
AuthUserFile                 /etc/proftpd/passwd

# VALID LOGINS
<Limit LOGIN>
   AllowUser webadmin
   DenyALL
</Limit>

<Directory /home/webadmin>
   <Limit ALL>
      DenyAll
   </Limit>
   <Limit DIRS READ WRITE>
      AllowUser webadmin
   </Limit>
</Directory>

Don't forget to restart the proftpd - daemon

/etc/init.d/proftpd restart
If you didn't change anything that was in there after the fresh install, this should be sufficient.

Give the FTP user access to /var/www directory

If you want to give this user access to any other directory like /var/www where normally the websites of the Apache2 server are located. You can mount the folder into the /home/webadmin folder.

These are the steps.
Create a new folder:
mkdir /home/webadmin/www
Mount the desired folder to the new folder:
sudo mount --bind -r /var/www /home/webadmin/www
Now you can login to the FTP server from any client computer by using a FTP client like FileZilla for example.

Regulate the access to read access, rather than write access

With this setup a user has read and write access to anything under /home/webadmin. To only give read permission you could change the setting in the config file to:

<Directory /home/webadmin>
   <Limit ALL>
      DenyAll
   </Limit>
   <Limit DIRS READ>
      AllowUser webadmin
   </Limit>
</Directory>

How to find the RSS feed address of a Twitter account

In the old Twitter you could go on a Twitter user's profile and on the bottom right there was a link that says: RSS feed of "XYZ's" tweets.

With the new Twitter these links don't exist anymore. 
There is a way to get this link though. Therefore you have to log out from Twitter and go to your or another twitter account by typing the URL: http://www.twitter.com/anyUser. Now you should be able to see the RSS feed and use it.

For example in PHP to embed the feed in your webside as described here.

Symfony arrays from actions turn into sfOutputEscaperArrayDecorator objects in template.

In symfony a common problem is that arrays that are passed on from the action to the template are turned into sfOutputEscaperArrayDecorator. Then functions like is_array($testArray) won't work anymore.
this->testArray = array('1'=>'a', '2'=>'b', '3'=>'c');


Turning output escaping off

This happens when you have output escaping enabled in Symfony.
If you don't need it you can turn it off in:
myapp/apps/frontend/config/settings.yml
Find the entry and set it to false.
all:
   escaping_strategy:    false


Solution with keeping output escaping enabled

In the action.class.php:
this->testArray = array('1'=>'a', '2'=>'b', '3'=>'c');
In the template.php:
$myArray = $sf_data->getRaw('testArray');

Wednesday, October 13, 2010

Example using OAuth-PHP to retrieve data from Twitter API

Similar to the OAuth-PHP example for Google Contacts we can retrieve data from Twitter in a similar way. The code is based on the examples from the OAuth-PHP page.

The example describes how to integrate the OAuth authentication with PHP in Symfony, when you take the code of the action class it self it can be used in any php file.

Get a twitter consumer key and consumer secret

Before you start coding you need to get a consumer key and consumer secret that allows your application to communicate with twitter.
To do so sign up for an account at Twitter and login.
Go to the Twitter API section and select Register a new application.

Make sure the callback URL is set up properly you need it later in the php code.

In the action.class.php:

public function executeSyncWithTwitter(sfWebRequest $request)
    {
        include_once sfConfig::get('sf_lib_dir')."/oauth/OAuthStore.php";
        include_once sfConfig::get('sf_lib_dir')."/oauth/OAuthRequester.php";

        define("TWITTER_CONSUMER_KEY",  "your_consumer_secret_here");
        define("TWITTER_CONSUMER_SECRET", "your_consumer_key_here");

        define("TWITTER_OAUTH_HOST", "https://twitter.com");
        define("TWITTER_REQUEST_TOKEN_URL", "https://api.twitter.com/oauth/request_token");
        define("TWITTER_AUTHORIZE_URL", "https://twitter.com/oauth/authorize");
        define("TWITTER_ACCESS_TOKEN_URL", "https://api.twitter.com/oauth/access_token");

        define('OAUTH_TMP_DIR', function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : realpath($_ENV["TMP"]));

        //  Init the OAuthStore
        $options = array(
                'consumer_key' => TWITTER_CONSUMER_KEY,
                'consumer_secret' => TWITTER_CONSUMER_SECRET,
                'server_uri' => TWITTER_OAUTH_HOST,
                'request_token_uri' => TWITTER_REQUEST_TOKEN_URL,
                'authorize_uri' => TWITTER_AUTHORIZE_URL,
                'access_token_uri' => TWITTER_ACCESS_TOKEN_URL
        );

        // Note: do not use "Session" storage in production. Prefer a database
        // storage, such as MySQL.
        OAuthStore::instance("Session", $options);

        try
        {
                //  STEP 1:  If we do not have an OAuth token yet, go get one
                if (empty($_GET["oauth_token"]))
                {
                        $getAuthTokenParams = array('oauth_callback' => 'your_domain.com/twitter/syncWithTwitter.php');

                        // get a request token
                        $tokenResultParams = OAuthRequester::requestRequestToken(TWITTER_CONSUMER_KEY, 0, $getAuthTokenParams);

                        //  redirect to the google authorization page, they will redirect back
                        $this->redirect(TWITTER_AUTHORIZE_URL . "?oauth_token=" . $tokenResultParams['token']);
                }
                else {
                        //  STEP 2:  Get an access token
                        $oauthToken = $_GET["oauth_token"];

                        $tokenResultParams = $_GET;

                        try {
                            OAuthRequester::requestAccessToken(TWITTER_CONSUMER_KEY, $oauthToken, 0, 'POST', $_GET);

                        }
                        catch (OAuthException2 $e)
                        {
                            //if there is any error send the user back to the homepage of your app
                            $this->redirect('@home');
                        }
                }
               
                //get the users information (see below for details about the return)
                $userContainerRequest = new OAuthRequester("https://twitter.com/account/verify_credentials.xml", 'GET', $tokenResultParams);
                $userContainer = $userContainerRequest->doRequest(0);
               
                if ($userContainer['code'] == 200) {
                    $this->user = new SimpleXMLElement($userContainer['body']);
                   }
        }
        catch(OAuthException2 $e) {
            //if there is any error send the user back to the homepage of your app
            $this->redirect('@home');
        }
        return sfView::SUCCESS;
    }

In the template syncWithTwitter.php:

All possible values of the twitter user object can be seen in the Twitter docs. The values from the SimpleXMLElement can be accessed as described below:

<h3>
Show Twitter user</h3>
<?php
echo $user->id; // e.g. 12345678
echo $user->name; // e.g. Peter Parker
echo $user->screen_name; // e.g. Spiderman
?>

In the routing.yml:

syncWithTwitter:
url:    /twitter/syncWithTwitter/*.
param:  { module: twitter, action: syncWithTwitter }

Friday, October 8, 2010

Get certain system path in a Symfony project

Sometimes you need to include a php file from certain system path in Symfony within a action.class.php.
The code to do that looks like be:
include_once sfConfig::get("sf_app_dir")."/lib/Helper.php";
Symfony has different keys that resolve to a certain directory in the project. The base directory in the below example is example_app in the Apache2 default folder /var/www.


sfConfig::get("sf_root_dir");          # /var/www/example_app
sfConfig::get("sf_apps_dir");          # /var/www/example_app/apps
sfConfig::get("sf_app_dir");           # /var/www/example_app/apps/frontend 
sfConfig::get("sf_app_config_dir");    # /var/www/example_app/apps/config
sfConfig::get("sf_app_i18n_dir");      # /var/www/example_app/apps/i18n
sfConfig::get("sf_app_lib_dir");       # /var/www/example_app/apps/lib
sfConfig::get("sf_app_module_dir");    # /var/www/example_app/apps/modules
sfConfig::get("sf_app_template_dir");  # /var/www/example_app/apps/templates
sfConfig::get("sf_cache_dir");         # /var/www/example_app/cache
sfConfig::get("sf_app_base_cache_dir");# /var/www/example_app/cache/frontend
sfConfig::get("sf_app_cache_dir");     # /var/www/example_app/cache/frontend/dev
sfConfig::get("sf_template_cache_dir");# /var/www/example_app/cache/frontend/dev/templates
sfConfig::get("sf_i18n_cache_dir");    # /var/www/example_app/cache/frontend/dev/i18n
sfConfig::get("sf_config_cache_dir");  # /var/www/example_app/cache/frontend/dev/config
sfConfig::get("sf_test_cache_dir");    # /var/www/example_app/cache/frontend/dev/test
sfConfig::get("sf_module_cache_dir");  # /var/www/example_app/cache/frontend/dev/modules
sfConfig::get("sf_config_dir");        # /var/www/example_app/config
sfConfig::get("sf_data_dir");          # /var/www/example_app/data
sfConfig::get("sf_doc_dir");           # /var/www/example_app/doc
sfConfig::get("sf_lib_dir");           # /var/www/example_app/lib
sfConfig::get("sf_log_dir");           # /var/www/example_app/log
sfConfig::get("sf_test_dir");          # /var/www/example_app/test
sfConfig::get("sf_plugins_dir");       # /var/www/example_app/plugins
sfConfig::get("sf_web_dir");           # /var/www/example_app/web
sfConfig::get("sf_upload_dir");        # /var/www/example_app/uploads

Joomla 1.5.20 - "Warning! - Failed to move file"

When you try to install any extension like  modules, components, plugins, languages or templates in Joomla in version 1.5.x an error could occur that says: "Warning! - Failed to move file".

That is because Joomla doesn't have the writing rights after a fresh install for the affected folders.
Following folders should be write enabled for any extension you upload:

joomla_base/tmp
joomla_base/language
(sometimes: joomla_base/logs)

Then depending on if you upload a administrator(backend) or a user(frontend) extension the according folder needs to be write enabled. That could be:

joomla_base/modules
joomla_base/components
joomla_base/plugins
joomla_base/templates
or
joomla_base/administrator/modules
joomla_base/administrator/components
joomla_base/administrator/language
joomla_base/administrator/templates

In Unix/Linux systems the CLI command would be:

chmod -R 777 language

Where -R stands for recursive all subfolders and files of the folder "language".

Warning: Failed to move file! in Joomla Media Manager

When you try to upload a file in Joomla 1.5 via the Media Manager of the Backend you could run into a similar error as described above.

Warning: Failed to move file!
Error. Unable to upload file.

To solve this problem you need to write enable the images folder in the joomla base path. Not the media folder!
chmod -R 777 images

Thursday, October 7, 2010

Forward certain URL(Domain) to specific folder on the Server with Apache2 Virtual Host and Debian5

If you have a Apache2 Web server that hosts different websites you might have them in different folders under /var/www (standard root folder in Apache2 on Debian5). For each project you have your own domain. Each domain should now point to a different folder.

For example you have mydomain.com, my2nddomain.com.
mydomain.com should hit /var/www/folder1/index.html and my2nddomain.com should display the index.html of /var/www/folder2/index.php (Lets assume folder2 contains a php application like Joomla).

First of all make sure that both domains point to the IP of your server. Normally Apache2 is running under port 80 so you don't need to specify the port.

Now open for example with vim or nano the Apache2 according Apache config file:

nano /etc/apache2/sites-enabled/000-default

Here you should add two entries at the very end of the file:



<virtualhost *:80>
ServerName mydomain.com
ServerAlias *.mydomain.com
DocumentRoot "/var/www/folder2"
DirectoryIndex index.html
<directory "/var/www/folder">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

<virtualhost *:80>
ServerName my2nddomain.com
ServerAlias *.my2nddomain.com
DocumentRoot "/var/www/folder2"
DirectoryIndex index.php
<directory "/var/www/folder2">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

Now don't forget to restart the Apache2 server with:

/etc/init.d/apache2 restart

And you are good to go.

Monday, October 4, 2010

JMeter - random boolean value in HTTP Request with JavaScript

If you are writing a load test with JMeter you often want to send in an random Boolean value with a HTTP request.

Following JavaScript code snippet can do that:
${__javaScript(Math.round(Math.random())==1 ? "true" : "false";)} 

What the code does in detail:

The surrounding $-sign with curly brackets

${...}
This signalizes to JMeter that a variable is used.

The javaScript tag

__javaScript()
This signalizes to JMeter to interpret the containing code as JavaScript.

The JavaScript Random function

Math.random()
Returns a random decimal value between 0 and 1.

The JavaScript Round function

Math.round()
Rounds values from .5 up to 1 and values below down to 0. So in 50% of the cases you get 0 and in the other 50% the value 1.


The IF shortcut

x==1 ? "true" : "false"
This expression stands for IF(?) x equals(==) 1 then return true else(:) false.

Other Purpose:

This code could also be used to send in a value randomly with the HTTP request or leave the value blank.
${__javaScript(Math.round(Math.random())==1 ? "12345" : "";)} 

Friday, October 1, 2010

Using url_for() in an Action - Symfony

In an symfony template you can get the base url of the application with:
url_for('@homepage', true);

Using this code in an action causes:
Fatal error: Call to undefined function url_for() in ...

In an action this code should be used instead:
$baseUrl = $this->getController()->genUrl('@homepage', true);

Using Oauth-php to retrieve email addresses from Google contacts (Gmail) in Symfony

The below code is a modification of the example code from the Oauth-php project. The example is for getting the 3 legged OAuth access to Google Docs.

Here the code was modified to get email addresses from Google contacts. That are basically all the email address that are used in Gmail.

The code was integrated in the Symfony framework. To get it running on a single PHP page only use the stuff in the action.class.php.

Of oauth-php project only the files in the library folder where used and copied into the symfony-project-base/lib/oauth.

For the rest a module gmail in symfony-project-base/apps/frontend/modules was created.

Symfony version 1.4.8 and oauth-php version 155.

in the action.class.php

public function executeStart(){

include_once sfConfig::get('sf_lib_dir')."/oauth/OAuthStore.php";
include_once sfConfig::get('sf_lib_dir')."/oauth/OAuthRequester.php";

define("GOOGLE_CONSUMER_KEY", sfConfig::get("app_google_consumer_key", "myDomain.com")); //
define("GOOGLE_CONSUMER_SECRET", sfConfig::get("app_google_consumer_secret", "myConsumerKey")); //

define("GOOGLE_OAUTH_HOST", "https://www.google.com");
define("GOOGLE_REQUEST_TOKEN_URL", GOOGLE_OAUTH_HOST . "/accounts/OAuthGetRequestToken");
define("GOOGLE_AUTHORIZE_URL", GOOGLE_OAUTH_HOST . "/accounts/OAuthAuthorizeToken");
define("GOOGLE_ACCESS_TOKEN_URL", GOOGLE_OAUTH_HOST . "/accounts/OAuthGetAccessToken");

define('OAUTH_TMP_DIR', function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : realpath($_ENV["TMP"]));

//  Init the OAuthStore
$options = array(
'consumer_key' => GOOGLE_CONSUMER_KEY,
'consumer_secret' => GOOGLE_CONSUMER_SECRET,
'server_uri' => GOOGLE_OAUTH_HOST,
'request_token_uri' => GOOGLE_REQUEST_TOKEN_URL,
'authorize_uri' => GOOGLE_AUTHORIZE_URL,
'access_token_uri' => GOOGLE_ACCESS_TOKEN_URL
);


// Note: do not use "Session" storage in production. Prefer a database
// storage, such as MySQL.
OAuthStore::instance("Session", $options);

try
{
//  STEP 1:  If we do not have an OAuth token yet, go get one
if (empty($_GET["oauth_token"]))
{
$getAuthTokenParams = array('scope' =>
'http://www.google.com/m8/feeds/contacts/default/base',
'xoauth_displayname' => 'Oauth test',
'oauth_callback' => 'http://myDomain.com/gmail/start');

// get a request token
$tokenResultParams = OAuthRequester::requestRequestToken(GOOGLE_CONSUMER_KEY, 0, $getAuthTokenParams);

$this->redirect(GOOGLE_AUTHORIZE_URL . "?btmpl=mobile&oauth_token=" . $tokenResultParams['token']);
}
else {
//  STEP 2:  Get an access token
$oauthToken = $_GET["oauth_token"];

$tokenResultParams = $_GET;

try {
OAuthRequester::requestAccessToken(GOOGLE_CONSUMER_KEY, $oauthToken, 0, 'POST', $_GET);

}
catch (OAuthException2 $e)
{
var_dump($e);
// Something wrong with the oauth_token.
// Could be:
// 1. Was already ok
// 2. We were not authorized
return;
}
}
// STEP 3: make the contacts request. Default is 25 contacts per request, the max that worked for me is 32.
$nextUrl = "http://www.google.com/m8/feeds/contacts/default/base?max-results=32";
$emailList = "";
while($nextUrl != ""){
$request = new OAuthRequester($nextUrl, 'GET', $tokenResultParams);
$result = $request->doRequest(0);
if ($result['code'] == 200) {
$oXML = new SimpleXMLElement($result['body']);

$nextUrl = "";
//parse for next url
foreach($oXML->link as $link){
$linkAttributes = $link->attributes();
$nextFlag = $linkAttributes["rel"];
if($nextFlag == "next"){
$nextUrl = $linkAttributes["href"];
//                                    echo "nextUrl: ".$nextUrl."
";
//replace user id with default, because otherwise the next call fails
$endOfFirstPart = strrpos($nextUrl,"feeds/contacts/");
$startOfSecondPart = strrpos($nextUrl,"/base?");

$urlFirstPart = substr($nextUrl,0,$endOfFirstPart+15);
$urlSecondPart = substr($nextUrl,$startOfSecondPart,strlen($nextUrl));
$nextUrl = $urlFirstPart."default".$urlSecondPart;
//                                    echo "nextUrl: ".$nextUrl."
";
break;
}
}

//parse for email entries 
foreach($oXML->entry as $oEntry){
$gd = $oEntry->children("http://schemas.google.com/g/2005");
foreach($gd->email as $emailNode){
$attributeArray = $emailNode->attributes();
$emailList = $emailList.$attributeArray["address"].",\n";
}
}
}
else {
echo 'Error';
}
}
$this->resultList = $emailList;
}
catch(OAuthException2 $e) {
echo "OAuthException:  " . $e->getMessage();
var_dump($e);
}
return sfView::SUCCESS;
}

in the template startSuccess.php

Show Gmail contacts

<?php echo $resultList; ?>

in the routing.yml

gmailStart:
url:    /gmail/start/*.
param:  { module: gmail, action: start }

Thursday, September 30, 2010

Include a PHP file in symfony

Sometimes you might want to include a file from an external library in symfony.

Copy the according library (third_party_lib) in symfony_project_folder/lib. To include a file from this folder in another php file do:

include_once sfConfig::get('sf_lib_dir')."/third_party_lib/File.php";

PHP get absolute path of file

To get the absolute path of a php file add:

<?php
$path = getcwd();
echo $path;
?>

Be aware:

This will give you the absolute path of the file in the file structure, not the URL!

Exception CURL error: SSL certificate problem, verify that the CA cert is OK

When using curl and sending an HTTPS request you could run into an error.

If you are getting "Exception CURL error: SSL certificate problem, verify that the CA cert is OK" or similar, that indicates problem with the remote SSL certificate.

To fix this you can add:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

to the curl request.

On the long run it is better to point to the certificate that curl should trust.

Wednesday, September 29, 2010

PHP Curl error numbers (errno:26)

I recently had a hard time finding what the error numbers stand for that are returned when sending an HTTP request with php_curl.
I was looking for error number 26. Error number 26 stands for:

CURLE_READ_ERROR (26)
There was a problem reading a local file or an error returned by the read callback.


A complete list of error codes can be found at the cURL project homepage.

Fatal error: Using $this when not in object context

Fatal error: Using $this when not in object context...

This has mostly two different reasons.

1. $this is used outside of a class.

class test{

   var $runtest;

   function test(){
      $this->runtest = 1; // proper usage
   }

   function printThis(){
      echo $this->runtest;
   }
}

$object = new test();
echo $this->runtest; // fails this not part of an object.
echo $object->runtest; // success 

2. The class method contains $this but was called in a static way.

class TestClass { 
    var $test; 

    function testMethod() { 
        $this->test = 'test'; 
    } 
} 

TestClass::testMethod(); // Error 

If the class needs to be used static the other variable or method needs to be called static as well.

class TestClass { 
    var $test; 

    function testMethod() { 
        TestClass::$test = 'test'; 
    } 
} 

Mamp and CURL

MAMP comes with the curl libary.

On the MAMP homepage you can find the what liberies of curl and other programs come with witch MAMP version.

MAMP Documentation