Contacts 디바이스 API 가이드 프로그램

개요

Contacts 디바이스 API 가이드 프로그램은 모바일 스마트 디바이스의 Contacts 관련 기능을 JavaScript 기반으로 제어 및 확인할 수 있도록 지원한다.
또한, 연락처 조회 기능과 연락처 정보를 서버로 백업하기 기능, 백업된 연락처 정보로 복구하기 기능 구현 시 참고 및 활용될 수 있도록 한다.

특징

본 가이드 프로그램 에서는 Contacts 기능을 가이드 할 수 있도록 연락처 조회 기능, 연락처 정보 백업 기능,
백업된 연락처로 복구 기능 를 제공하고 있다.

기능 흐름도

기능 시퀀스

전제 조건

구분 내용
Local 디바이스 개발 환경 Xcode 4.5.2, PhoneGap 2.9.0
서버 사이드 개발 환경 전자정부표준프레임워크 개발환경2.7
Mash up Open API 연계 N/A
테스트 디바이스 iPhone4
테스트 플랫폼 iOS 5.1
추가 라이브러리 적용 N/A

제약 사항

지원 디바이스 및 플랫폼

iPhone 의 경우 장치의 연산속도에 따른 문제가 발생할 수 있다.
  • 문제 : 폰갭 오류시.
  • 해결방안 : setTimeout() 함수로 phonegap 로딩 순서를 지연시킨다.
    document.addEventListener('DOMContentLoaded', function () { setTimeout(loaded, 200); }, false);
  • 문제 : iScroll4 컨텐츠 내용 높이 계산 오류.
  • 해결방안 : setTimeout() 함수로 컨텐츠에 css 적용이 완료되고 iscroll 생성이 되도록 한다.
setTimeout(function()
           {
               myScroll = new iScroll(thisPage, 
                                      { 
                                          checkDOMChanges: true,
                                          onBeforeScrollStart:function(e)
                                          {
                                          }
                                      });
           },
           500);
연락처 수정이나 생성시 새로운 ID가 부여된다
  • 문제 : 폰갭 라이브러리를 사용해서 연락처를 복원할때 복원된 ID는 새로 부여된다.
  • 해결 방안 : 복원된 새로운 ID를 서버로 동기화 하여 해결.
Xcode 4.5에서 실행 시 주의 사항

Xcode 4.5부터 iOS 6, iPhone 5를 지원하는데, 해당 디바이스와 OS를 지원을 할 경우 PhoneGap 1.9.0의 라이브러리는 Xcode 4.4.1에서 컴파일되어 배포되는 라이브러리라 프로젝트 옵션을 조정할 필요가 있다.

1. 옵션 설정

2. armv7s 삭제

function fn_egov_contactIdUpdate() 
{
    if(fn_egov_network_check(false))        // 통신 이벤트 발생시 3G 사용자 승인여부 판단
    {
        var params = {contactsList : {contactsList : JSON.stringify(recoveryParam)}};
        
        $.mobile.showPageLoadingMsg('a');
        // 디바이스의 성능에 따라 PrograssDialog Show 가 완전이 로딩이 된후 다음 코드를 수행하도록 setTimeout 을 사용하여 지연시킨다.
        setTimeout(function()
                   {
                       fn_egov_sendto_server("/ctt/updateContactsiOS.do",params);
                   }, 
                   500);
    }
}

크로스 도메인 사용

폰갭에서 특정 외부 도메인이나 외부 도메인의 하위 도메인을 사용해야할 경우,
[Project_Name]/Supproting Files/config.xml에서 <access origin=“ExternalHosts”/> 항목에 외부 도메인 주소를 추가 설정해야 외부 도메인에 접속할 수 있다.

라이선스

N/A

설명

Contacts 디바이스API 가이드 프로그램은 크게 Contacts 연락처 조회, 연락처 정보 백업, 백업된 연락처로 복구
기능으로 구성되어 있다.

관련 클래스다이어그램

Device Application

관련 소스

유형대상소스비고
CSS www/css/egovframwork/mbl/hyb/ContactsAPI.css ContactsAPI 가이드 프로그램 주요 Cascading Style Sheets
IMAGE www/images/egovframwork/mbl/hyb/ ContactsAPI 가이드 프로그램 주요 Image 폴더
JS www/js/egovframwork/mbl/hyb/ContactsAPI.js ContactsAPI 가이드 프로그램 주요 JavaScript
HTML www/ContactsAPI.htmlContactsAPI 메인 페이지
HTML www/license.htmlContactsAPI 라이센스 페이지
HTML www/overview.htmlContactsAPI 기능설명 페이지

활용 API

contacts.create
  • 연락처 객체를 생성한다.
var myContact = navigator.contacts.create({"displayName": "Test User"});
contacts.find
  • 연락처를 검색 한다.
navigator.contacts.find(contactFields, contactSuccess, contactError, contactFindOptions);
var options = new ContactFindOptions();
options.filter="Bob"; 
var fields = ["displayName", "name"];
navigator.contacts.find(fields, onSuccess, onError, options);
  • 연락처를 저장 한다.
// create a new contact object
var contact = navigator.contacts.create();
contact.displayName = "Plumber";    // not support iOS
contact.nickname = "Plumber";       //specify both to support all devices

// populate some fields
var name = new ContactName();
name.givenName = "Jane";
name.familyName = "Doe";
contact.name = name;

// save to device
contact.save(onSuccess,onError);
  • 연락처를 복사 한다.
var clone = contact.clone();
clone.name.givenName = "John";
console.log("Original contact name = " + contact.name.givenName);
console.log("Cloned contact name = " + clone.name.givenName);
  • 연락처를 제거 한다.
function onSuccess() {
    alert("Removal Success");
};

function onError(contactError) {
    alert("Error = " + contactError.code);
};

// remove the contact from the device
contact.remove(onSuccess,onError);

- contactFields

Properties객체구조비고
ID(DOMString)글로벌 고유 식별자.
displayName(DOMString)최종 사용자에게 표시하기에 적합이 연락처의 이름.
name(ContactName)명 이름의 모든 구성 요소를 포함하는 개체입니다.
nickname(DOMString)연락을하여 해결할 수있는 캐주얼 이름.
phoneNumbers(ContactField [])모든 연락처의 전화 번호의 배열입니다.
email(ContactField [])모든 연락처의 이메일 주소의 배열입니다.
addresses(ContactAddress [])연락처의 주소를 모든 배열입니다.
ims(ContactField [])모든 연락처의 IM 주소의 배열입니다.
organizations(ContactOrganization [])연락처의 조직 전체의 배열입니다.
birthdayDate연락처의 생일. (일)
note(DOMString)연락처에 대한 참고 사항.
photo(ContactField [])연락처의 사진의 배열입니다.
categories(ContactField [])사용자 정의 범주의 모든 연락처의 배열입니다.
urls(ContactField [])연락처에 관련된 웹 페이지의 배열입니다.

- ContactName

Properties객체구조비고
formatted(DOMString)연락처의 전체 이름
familyName(DOMString)
givenName(DOMString)
middleName(DOMString)
honorificPrefix(DOMString)연락처 접두사
honorificSuffix(DOMString)연락처 접미사

- ContactField

Properties객체구조비고
type(DOMString)필드의 종류를 구분할수 있는 문자열
value(DOMString)필드의 값
pref(Boolean)선호 가치 표시

- ContactAddress

Properties객체구조비고
pref(boolean)ContactAddress 객체의 대표 값 여부
type(DOMString)필드 유형을 정의한다
formatted(DOMString)출력을 위한 전체 주소
streetAddress(DOMString)동 / 읍 / 면 / 가 와 같은 전체 주소
locality(DOMString)’시’ / ‘도’ 표기
region(DOMString)’주’ / ‘지방’ 표기
postalCode(DOMString)우편번호
country(DOMString)국번

- ContactOrganization

Properties객체구조비고
pref(Boolean)ContactOrganization 객체의 대표 값 여부
type(DOMString)필드 유형을 정의한다(예: ‘work’ 은 직장.)
name(DOMString)소속의 이름을 정의한다
department(DOMString)소속 부서를 정의한다
title(DOMString)소속 내의 직함을 정의한다

- ContactFindOption

Properties객체구조비고
filter(DOMString)검색어 또는 검색조건을 정의한다. (Default: ””)
multiple(Boolean)검색 결과으로 다수의 연락처를 가져올 것인지의 여부

- ContactError

Properties객체구조비고
ContactError.UNKNOWN_ERROR(DOMString)알수 없는 에러
ContactError.INVALID_ARGUMENT_ERROR(DOMString)잘못된 인자값
ContactError.TIMEOUT_ERROR(DOMString)응답시간 지연
ContactError.PENDING_OPERATION_ERROR(DOMString)잘못된 명령
ContactError.IO_ERROR(DOMString)입출력 에러
ContactError.NOT_SUPPORTED_ERROR(DOMString)지원하지 않음
ContactError.PERMISSION_DENIED_ERROR(DOMString)권한 에러

Server Application

관련 소스

유형대상소스비고
Controlleregovframework.hyb.ios.ctt.web.EgovContactsiOSAPIController.java연락처 정보 관리를 위한 클래스
Serviceegovframework.hyb.ios.ctt.service.EgovContactsiOSAPIService.java연락처 정보 관리를 위한 서비스 인터페이스
VOegovframework.hyb.ios.ctt.service.ContactsiOSAPIDefaultVO.java연락처 관리를 위한 VO 클래스
VOegovframework.hyb.ios.ctt.service.ContactsiOSAPIVO.java연락처 관리를 위한 VO 클래스
DAOegovframework.hyb.ios.ctt.service.impl.ContactsiOSAPIDAO.java연락처 관리를 위한 데이터처리 클래스
DAOegovframework.hyb.ios.ctt.service.impl.EgovContactsiOSAPIServiceImpl.java요청 서비스 처리를 위한 클래스
Query XMLresources/egovframework/sqlmap/hyb/ios/ctt/EgovContactsiOSAPIGuide_SQL_mysql.xml연락처 관리를 위한 Query 파일

관련 테이블

테이블명

테이블명테이블명(영문)비고
연락처Contacts연락처 백업 정보

테이블 명세서

연락처
No.컬럼컬럼명타입길이NullKEY
1ID일련번호VARCHAR20NotNullpk
2UUIDUUIDVARCHAR50NotNullpk
3NAME이름VARCHAR60Null
4TELNO전화번호VARCHAR20Null
5EMAILS이메일VARCHAR50Null
6USEYN활성화여부CHAR1Null

ERD

환경 설정

Contacts 디바이스 API 가이드 프로그램에서 제공하는 모바일 디바이스의 Contacts 관련 기능을 활용하기 위하여 필요한 항목 및 그 환경 설정은 다음과 같다.

Device Application

config.xml

    <feature name="Contacts">
        <param name="ios-package" value="CDVContacts"/>
    </feature>

Server Aplication

resource/egovframework/sqlmap/sql-map-config_[DB명].xml

<sqlMap resource="egovframework/sqlmap/hyb/ios/ctt/EgovContactsiOSAPIGuide_SQL_[DB명].xml"/>

관련 기능

Contacts 디바이스API 가이드 프로그램은 크게 Contacts 연락처 조회, 연락처 정보 백업, 백업된 연락처로 복구 기능으로 구성되어 있다.

연락처 조회

비즈니스 규칙

1. 네트워크를 체크하여 Wi-Fi가 아닐 경우에는 사용자 승인여부에 따라 동작한다.

관련 코드

연락처를 조회 한다
function fn_egov_move_localContactList()
{
    // 최초 실행시 사용자 승인 여부 체크
    if(check3G)
    {
        if(fn_egov_network_check(true))        // 최초 앱 실행시 3G 사용자 승인여부 판단
        {
            check3G = false;
        }
        return;
    }
    
    $.mobile.showPageLoadingMsg('a');
    var obj = new ContactFindOptions();
    obj.filter = "";
    obj.multiple = true;
    var fields = ["*"];
    navigator.contacts.find(fields, fn_egov_contactListSuccess, fn_egov_fail, obj);
}
연락처를 조회 성공
function fn_egov_contactListSuccess(contacts) 
{
    console.log("DeviceAPIGuide fn_egov_contactListSuccess");
    
    var theList = $('#listView');
    theList.html("");
    contactsList = contacts;	
    nPageCount = 0;
    fn_egov_showList(true);
	
    $.mobile.hidePageLoadingMsg('a');
    ProcessName = "contactList";
    $.mobile.changePage("#contactList", "slide", false, false);
	
    theList.listview("refresh");
	
    setTimeout(loadiScrollList, 1000);
}
특정 연락처 상세조회
function fn_egov_move_localContactDetail(index)
{
    var displayName = "";
    var phoneNumber = "";
    var email = "";
    var ContactName = "";
    
    if(contactsList[index].name.formatted != null) 
    {
        ContactName = contactsList[index].name.formatted;
    }
	
    if(contactsList[index].phoneNumbers != null) 
    {
    	for(var j=0; j<contactsList[index].phoneNumbers.length; j++) 
        {
    	    if(contactsList[index].phoneNumbers[j].type == "mobile")
            {
    		phoneNumber = contactsList[index].phoneNumbers[j].value;
    		break;
    	    }
        }
    }
    if(contactsList[index].emails != null)
    {
        for(var j=0; j<contactsList[index].emails.length; j++) 
        {
            email = contactsList[index].emails[j].value;
            break;
        }
    }
    
    document.getElementById('detailIndex').value = index;
    document.getElementById('detailName').value = ContactName;
    document.getElementById('detailPhone').value = phoneNumber;
    document.getElementById('detailEmail').value = email;
    ProcessName = "contactDetail";
    $.mobile.changePage("#contactDetail", "slide", false, false);
}
연락처 삭제

function fn_egov_contactLocalDelete() {

var index = $("#detailIndex").val();
contactsList[index].remove(fn_egov_removeSuccess,fn_egov_fail);

} </code>

관련 화면 및 수행매뉴얼

기능_URL_Controllermethod화면(HTML)
연락처 목록 조회 fn_egov_contactListSuccessContactsAPI.html#contactList
연락처 상세 조회 fn_egov_move_localContactDetailContactsAPI.html#contactDetail
연락처 삭제 fn_egov_contactLocalDeleteContactsAPI.html#contactDetail


리스트:연락처의 상세 조회를 호출한다.
+ 10개더보기:연락처 목록 10개를 더 보여준다.
삭제:연락처를 삭제 한다

연락처 정보 백업

비즈니스 규칙

1. 네트워크를 체크하여 Wi-Fi가 아닐 경우에는 사용자 승인여부에 따라 동작한다.
2. 연락처 백업시 이전에 백업된 연락처와 같은 ID 는 업데이트 없는 ID 는 새로 추가한다.

관련 코드

서버에 백업된 연락처 총 갯수 요청
function fn_egov_contactBackupCountSuccess(contacts) 
{	
    console.log("DeviceAPIGuide fn_egov_contactBackupCountSuccess");
    contactsList = contacts;
    
    var params = {uuid :  device.uuid};
	
    $.mobile.showPageLoadingMsg('a');
    // 디바이스의 성능에 따라 PrograssDialog Show 가 완전이 로딩이 된후 다음 코드를 수행하도록 setTimeout 을 사용하여 지연시킨다.
    setTimeout(function()
               {
                   fn_egov_sendto_server("/ctt/selectBackupCountiOS.do",params);
               }, 
               500);
}
연락처 정보를 서버로 백업한다.
function fn_egov_contactBackup() 
{
    if(contactsList.length == 0) 
    {
        return;
    }
    
    if(fn_egov_network_check(false))        // 통신 이벤트 발생시 3G 사용자 승인여부 판단
    {
        $.mobile.showPageLoadingMsg('a');
        ProcessName = "contactBackup";   
        var param = [];	
        for(var index = 0; index < contactsList.length ; index++) 
        {
            var displayName = '';
            var phoneNumber = '';
            var email = '';
            
            if(contactsList[index].name.formatted != null) 
            {
                displayName = contactsList[index].name.formatted;
            }
            
            if(contactsList[index].phoneNumbers != null) 
            {
                for(var j=0; j<contactsList[index].phoneNumbers.length; j++) 
                {
                    if(contactsList[index].phoneNumbers[j].type == "mobile")
                    {
                        phoneNumber = contactsList[index].phoneNumbers[j].value;
                        break;
                    }
                }
            }
            if(contactsList[index].emails != null) 
            {
                for(var j=0; j<contactsList[index].emails.length; j++) 
                {
                    email = contactsList[index].emails[j].value;
                    break;
                }
            }
            
            param.push({uuid:device.uuid, id:contactsList[index].id, name:encodeURIComponent(displayName), telNo:phoneNumber, useYn:'Y', emails:email});
        }
        
        var params = {contactsList : {contactsList : JSON.stringify(param)}};
        // var params = {contactsList : {contactsList : param}};
        // 디바이스의 성능에 따라 PrograssDialog Show 가 완전이 로딩이 된후 다음 코드를 수행하도록 setTimeout 을 사용하여 지연시킨다.
        setTimeout(function()
                   {
                       fn_egov_sendto_server("/ctt/addContactsiOSInfo.do",params);
                   }, 
                   500);
    }
}

관련 화면 및 수행매뉴얼

기능_URL_Controllermethod화면(HTML)
백업된 연락처 총 갯수 조회/ctt/selectBackupCountiOS.doEgovContactsiOSAPIControllerselectContactsCountContactsAPI.html#contactBackup
연락처 백업 요청/ctt/addContactsiOSInfo.doEgovContactsiOSAPIControlleraddContactsInfoXmlContactsAPI.html#contactBackup


연락처백업:디바이스의 연락처를 서버로 백업한다.

백업된 연락처로 복구하기

비즈니스 규칙

1. 네트워크를 체크하여 Wi-Fi가 아닐 경우에는 사용자 승인여부에 따라 동작한다.
2. 연락처 복구시 디바이스의 연락처와 백업된 연락처가 겹치는 경우 디바이스의 연락처를 삭제하고 백업된 연락처로 복구 한다.

관련 코드

서버에 백업된 연락처 총 갯수 요청
function fn_egov_contactBackupCountSuccess(contacts) 
{	
    console.log("DeviceAPIGuide fn_egov_contactBackupCountSuccess");
    contactsList = contacts;
    
    var params = {uuid :  device.uuid};
	
    $.mobile.showPageLoadingMsg('a');
    // 디바이스의 성능에 따라 PrograssDialog Show 가 완전이 로딩이 된후 다음 코드를 수행하도록 setTimeout 을 사용하여 지연시킨다.
    setTimeout(function()
               {
                   fn_egov_sendto_server("/ctt/selectBackupCountiOS.do",params);
               }, 
               500);
}
백업된 연락처로 복구하기
function contactRecoveryCallBack(ContactData)
{
    var id = ContactData["id"];
    var name = ContactData["name"];
    var telNo = ContactData["telNo"];
    var email = ContactData["emails"];
    
    var options = new ContactFindOptions();
    options.filter=String(id); 
    var fields = ["id"];
    navigator.contacts.find(fields, 
                            function(contacts)
                            {					    						          
                                if(contacts.length != 0) 
                                {
                                    contacts[0].remove(function(contact){},fn_egov_fail);
                                }
                            
                                var contactname = new ContactName();
                                contactname.middleName = name;
                            
                                var myContact = navigator.contacts.create({"name":contactname});
                            
                                var phoneNumbers = [2];
                                phoneNumbers[0] = new ContactField('work', telNo, false);
                                phoneNumbers[1] = new ContactField('mobile', telNo, false); 
                                phoneNumbers[2] = new ContactField('home', telNo, false); 
                                myContact.phoneNumbers = phoneNumbers;
                            
                                var emails = [2];
                                emails[0] = new ContactField('work', email, false);
                                emails[1] = new ContactField('home', email, false);
                                myContact.emails = emails;
                            
                                myContact.save(function(contact)
                                               {					            		
                                                   recoveryParam.push({uuid:device.uuid, contactId:id, newId:contact.id});
                                                   if(listCount === recoveryParam.length)
                                                   {
                                                       $.mobile.hidePageLoadingMsg('a');
                                                       confirm(String(listCount) + '개의 연락처가 복원되었습니다. 저장된 연락처 정보를 서버와 동기화 합니다.');
                                                       $.mobile.showPageLoadingMsg('a');
                                                       fn_egov_contactIdUpdate();	
                                                   }
                                               },fn_egov_fail);					            					            	
                            
			    }, function(contactError){}, options);
}
복구한 연락처 서버로 동기화
function fn_egov_contactIdUpdate() 
{
    if(fn_egov_network_check(false))        // 통신 이벤트 발생시 3G 사용자 승인여부 판단
    {
        var params = {contactsList : {contactsList : JSON.stringify(recoveryParam)}};
        
        $.mobile.showPageLoadingMsg('a');
        // 디바이스의 성능에 따라 PrograssDialog Show 가 완전이 로딩이 된후 다음 코드를 수행하도록 setTimeout 을 사용하여 지연시킨다.
        setTimeout(function()
                   {
                       fn_egov_sendto_server("/ctt/updateContactsiOS.do",params);
                   }, 
                   500);
    }
}

관련 화면 및 수행매뉴얼

기능_URL_Controllermethod화면(HTML)
백업된 연락처 총 갯수 조회/ctt/selectBackupCountiOS.doEgovContactsiOSAPIControllerselectContactsCountContactsAPI.html#contactBackup
백업된 연락처로 복구/ctt/contactsiOSInfoList.doEgovContactsiOSAPIControllerselectContactsInfoListXmlContactsAPI.html#contactRecovery
복구한 연락처 서버로 동기화/ctt/updateContactsiOS.doEgovContactsiOSAPIControllerupdateContactsiOSContactsAPI.html#contactRecovery


연락처복원:서버에 백업된 연락처로 복구한다.

컴파일 디버깅 배포

컴파일

Device Application

  1. ContactsAPI 프로젝트의 설치 상태를 확인하고 빌드를 클릭한다

  • 빌드가 성공하고 나면 다음과 같은 화면이 실행된다.

Server Application

  1. 프로젝트 우클릭>Run as>Maven install 을 클릭하여 빌드를 한다.

  2. 빌드 성공 (콘솔 정보 확인)

  3. 프로젝트 우클릭>Run as>Run on Server 를 클릭하여 실행시킨다.

  4. 프로젝트가 성공적으로 실행되었는지 확인한다.

디버깅

디바이스 어플리케이션에서 발생한 오류 내용 확인 및 디버깅을 위해서는 폰갭 프레임워크에서 제공하는 console.log를 이용할 수 있다. console.log 함수는 자바스크립트 구문에서 사용할 수 있는 디버그 코드로 이클립스 및 Xcode에서 확인 할 수 있다.

  • 실제 콘솔 로그 예
function fn_egov_network_check(doCheck)
{
    console.log('DeviceAPIGuide fn_egov_network_check');
    var networkState = navigator.network.connection.type;
    ...
}
  • xCode 콘솔 창


  • Organizer 로그 창


로그 메시지비고
DeviceAPIGuide fn_egov_network_check네트워크 상태 체크
DeviceAPIGuide fn_egov_contactListSuccess디바이스내 연락처 목록 조회 성공
DeviceAPIGuide fn_egov_fail + errorCode에러코드 사유로 인해 Contacts Device API 실패
DeviceAPIGuide fn_egov_removeSuccess연락처 삭제 성공
DeviceAPIGuide fn_egov_sendto_server Response Completed서버에 요청한 내용이 성공적으로 처리 된 경우
DeviceAPIGuide fn_egov_sendto_server Response Failed서버 내부 처리 오류
DeviceAPIGuide fn_egov_sendto_server Request Failed통신 오류
DeviceAPIGuide fn_egov_contactBackupCountSuccess백업된 연락처 갯수 조회 성공
DeviceAPIGuide fn_egov_contactRecoveryCountSuccess연락처 복구 하기전 조회 성공

배포

Contacts 디바이스 API 가이드 다운로드 : Click

참고 자료

  • UX/UI 라이브러리 : jQuery MobileClick
  • Phonegap 2.9.0 Contacts : Click
 
egovframework/hyb2.7/guide/ios/contact.txt · 마지막 수정: 2015/04/14 11:24 (외부 편집기)
 
이 위키의 내용은 다음의 라이센스에 따릅니다 :CC Attribution-Noncommercial-Share Alike 3.0 Unported
전자정부 표준프레임워크 라이센스(바로가기)

전자정부 표준프레임워크 활용의 안정성 보장을 위해 위험성을 지속적으로 모니터링하고 있으나, 오픈소스의 특성상 문제가 발생할 수 있습니다.
전자정부 표준프레임워크는 Apache 2.0 라이선스를 따르고 있는 오픈소스 프로그램입니다. Apache 2.0 라이선스에 따라 표준프레임워크를 활용하여 발생된 업무중단, 컴퓨터 고장 또는 오동작으로 인한 손해 등에 대해서 책임이 없습니다.
Recent changes RSS feed CC Attribution-Noncommercial-Share Alike 3.0 Unported Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki