作者彙整: johnpupu

error 0無法連接 iTunes Store ( In-App Purchase )

最近在實作 In-App Purchase
在 iPhone 上測試的時後一直吃到屎….
不斷出現…”error 0 無法連接 iTunes Store”的錯誤.
搞了一兩天…..
最後google了一堆大家的問題
有的說要先上傳 Binary 然後 Reject 才會測通..
也照幹了…
還是失敗…
後來把整個 product 砍掉, 再重建新的…
test user 也重新建…
結果在萬念俱灰的情況下….
他通了……他通了……他通了……他通了……他通了……他通了……他通了……
這篇單純是筆紀一下…

1. 可能要先上傳 Binary ( 近期會再用另一個 Apple id 來建新 App 就可以知道拔獅子的鬃毛會不會長頭髮出來了 )
2. Versions Status : Developer Rejected
3. 在 App details 中的 In-App Purchase 未將Product 加入.
4. 做完這些後重新建一個 test user

iPhone/iPod Touch: application executable is missing a required architecture. At least one of the following architecture(s) must be present: armv6

如果在 Xcode ORganizer 上 Validate 發生
iPhone/iPod Touch: application executable is missing a required architecture. At least one of the following architecture(s) must be present: armv6
的錯誤的話

可以參考
http://stackoverflow.com/questions/4198676/warning-iphone-apps-should-include-an-armv6-architecture-even-with-build-config/6833556#6833556
這篇在 Targets->Build Settings 底下的 Architectures 把 Release 加上 armv6
然後要是一樣還是沒過的話
請檢查 Targets->Build Settings 底下的 Valid Architectures 一樣加上 armv6
這樣應該就會過了…
不過這樣可能會有非常非常多的 Warnings……

所以請回頭檢查你的 Targets->Summary->Deployment Target 是多少的版本…
我原本的版本是 4.2…, 在我把 Deployment Target 改成 5.0 後, 上面文章說的所有設定都不用動…用原本的設定, validate 就會過了…

我猜想會有這樣的錯誤應該是因為 Deployment Target 要符合的版本太久..所以須要 armv6, 來符合一些較久之前的 Devices ??

wkhtmltopdf中文字型

安裝 /usr/ports/converters/wkhtmltopdf/
安裝 /usr/ports/x11-fonts/xorg-fonts-truetype
下載 http://cle.linux.org.tw/fonts/cwttf/cwttf-v1.0.tar.gz
cp * /usr/local/lib/X11/fonts/TTF
( cwttf 的連結已經消失 )
在下面的連結抓新的字型
http://code.google.com/p/cwtex-q-fonts/
然後
mkdir -p /usr/local/lib/X11/fonts/TTF/cwTeX/baseline
將cwTeX的字放在baseline底下
fc-cache -f -v

搞定

 

Yii ActiveRecord空字串存成Null

因為搞了快1個小時
搞定後~~筆記下來…

直接下Models 裡面的 rules()
設定

public function rules()
{
  array('column1,column2', 'default', 'setOnEmpty' => true, 'value' => null),
}

ref. http://www.yiiframework.com/forum/index.php?/topic/5448-blank-fields-in-forms-insert-replace-default-null-columns-with-empty-string/

Cordova / Phonegap PushNotification tutorial ( iOS限定 )

前言
執行下列monkey step 前, 請先閱讀 Phonegap/Cordova 的 installation guide 建立一個 Project 出來
我用到的 Phonegap Plugin 是 PushNotification, 及加上  EasyAPNS

1. 先產生 CSR.
2. 在 iOS Provisioning Portal 中 new 一個 APP IDs, 然後 configure,  Enable for Development, Enable for Production
3. 下載 aps_development.cer, aps_production.cer, double click 這兩個file 匯進key acess
4. 建立一個 新的 Provisioning profile 選擇剛剛新建的  App IDs.
5. 下載此Provisioning profile.
6. 打開 Xcode, 選擇organizer 把 Provisioning profile 的檔案 拉進去
7. 選擇 Project, 將Build Settings 裡面的Code Signing identity 選擇成 你所建立的 App ID
8. 在Classes 按右鍵選 Add Files to “xxx”, 將這兩個檔案加上去 PushNotification.m, PushNotification.h 這兩個檔案 ( PushNotification 是 Phonegap 的Plugin )
9. 放 PushNotification.js, jquery 及 jquery mobile 的相關檔案 放到 www folder
10. 開啟 Cordova.plist

  • 在 ExternalHosts 加上 一筆 * 的 record , key 用default 的即可。
  • 在 Plugins 加上一筆 key -> PushNotification, value -> PushNotification

11. 打開 AppDelegate.h 加上

#import <AudioToolbox/AudioToolbox.h>
#import "PushNotification.h"

12. 打開 AppDelegate.m

  1. 在 didFinishLaunchingWithOptions 這個 method 裡上方加上
    // Add registration for remote notifications
    [[UIApplication sharedApplication]
    	registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
    
    // Clear application badge when app launches
    application.applicationIconBadgeNumber = 0;
  2. 在 didFinishLaunchingWithOptions 這個 method 裡頭的 [self.window makeKeyAndVisible]; 和 return YES; 中間插
    [self.window makeKeyAndVisible];
    
    /* START BLOCK */
    
    // PushNotification - Handle launch from a push notification
    NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if(userInfo) {
        PushNotification *pushHandler = [self.viewController getCommandInstance:@"PushNotification"];
        NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];
        [mutableUserInfo setValue:@"1" forKey:@"applicationLaunchNotification"];
        [mutableUserInfo setValue:@"0" forKey:@"applicationStateActive"];
        [pushHandler.pendingNotifications addObject:mutableUserInfo];
    }
    
    /* STOP BLOCK */
    
    return YES;
  3. dealloc method and the implementation @end. 中間插入
    - (void) dealloc
    {
    	[super dealloc];
    }
    
    /* START BLOCK */
    
    /**
     * Fetch and Format Device Token and Register Important Information to Remote Server
     */
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    
    #if !TARGET_IPHONE_SIMULATOR
        /* EasyAPNS Start Block */
    
    	// Get Bundle Info for Remote Registration (handy if you have more than one app)
    	NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
    	NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
    
    	// Check what Notifications the user has turned on.  We registered for all three, but they may have manually disabled some or all of them.
    	NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
    
    	// Set the defaults to disabled unless we find otherwise...
    	NSString *pushBadge = @"disabled";
    	NSString *pushAlert = @"disabled";
    	NSString *pushSound = @"disabled";
    
    	// Check what Registered Types are turned on. This is a bit tricky since if two are enabled, and one is off, it will return a number 2... not telling you which
    	// one is actually disabled. So we are literally checking to see if rnTypes matches what is turned on, instead of by number. The "tricky" part is that the
    	// single notification types will only match if they are the ONLY one enabled.  Likewise, when we are checking for a pair of notifications, it will only be
    	// true if those two notifications are on.  This is why the code is written this way
    	if(rntypes == UIRemoteNotificationTypeBadge){
    		pushBadge = @"enabled";
    	}
    	else if(rntypes == UIRemoteNotificationTypeAlert){
    		pushAlert = @"enabled";
    	}
    	else if(rntypes == UIRemoteNotificationTypeSound){
    		pushSound = @"enabled";
    	}
    	else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert)){
    		pushBadge = @"enabled";
    		pushAlert = @"enabled";
    	}
    	else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)){
    		pushBadge = @"enabled";
    		pushSound = @"enabled";
    	}
    	else if(rntypes == ( UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)){
    		pushAlert = @"enabled";
    		pushSound = @"enabled";
    	}
    	else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)){
    		pushBadge = @"enabled";
    		pushAlert = @"enabled";
    		pushSound = @"enabled";
    	}
    
    	// Get the users Device Model, Display Name, Unique ID, Token & Version Number
    	UIDevice *dev = [UIDevice currentDevice];
    	NSString *deviceUuid = dev.uniqueIdentifier;
        NSString *deviceName = dev.name;
    	NSString *deviceModel = dev.model;
    	NSString *deviceSystemVersion = dev.systemVersion;
    
    	// Prepare the Device Token for Registration (remove spaces and < >)
    	NSString *deviceToken = [[[[devToken description]
                                   stringByReplacingOccurrencesOfString:@"<"withString:@""]
                                  stringByReplacingOccurrencesOfString:@">" withString:@""]
                                 stringByReplacingOccurrencesOfString: @" " withString: @""];
    
    	// Build URL String for Registration
    	// !!! CHANGE "www.mywebsite.com" TO YOUR WEBSITE. Leave out the http://
    	// !!! SAMPLE: "secure.awesomeapp.com"
    	NSString *host = @"www.example.com/apns";
    
    	// !!! CHANGE "/apns.php?" TO THE PATH TO WHERE apns.php IS INSTALLED
    	// !!! ( MUST START WITH / AND END WITH ? ).
    	// !!! SAMPLE: "/path/to/apns.php?"
    	NSString *urlString = [@"/apns.php?"stringByAppendingString:@"task=register"];
    
    	urlString = [urlString stringByAppendingString:@"&appname="];
    	urlString = [urlString stringByAppendingString:appName];
    	urlString = [urlString stringByAppendingString:@"&appversion="];
    	urlString = [urlString stringByAppendingString:appVersion];
    	urlString = [urlString stringByAppendingString:@"&deviceuid="];
    	urlString = [urlString stringByAppendingString:deviceUuid];
    	urlString = [urlString stringByAppendingString:@"&devicetoken="];
    	urlString = [urlString stringByAppendingString:deviceToken];
    	urlString = [urlString stringByAppendingString:@"&devicename="];
    	urlString = [urlString stringByAppendingString:deviceName];
    	urlString = [urlString stringByAppendingString:@"&devicemodel="];
    	urlString = [urlString stringByAppendingString:deviceModel];
    	urlString = [urlString stringByAppendingString:@"&deviceversion="];
    	urlString = [urlString stringByAppendingString:deviceSystemVersion];
    	urlString = [urlString stringByAppendingString:@"&pushbadge="];
    	urlString = [urlString stringByAppendingString:pushBadge];
    	urlString = [urlString stringByAppendingString:@"&pushalert="];
    	urlString = [urlString stringByAppendingString:pushAlert];
    	urlString = [urlString stringByAppendingString:@"&pushsound="];
    	urlString = [urlString stringByAppendingString:pushSound];
    
    	// Register the Device Data
    	// !!! CHANGE "http" TO "https" IF YOU ARE USING HTTPS PROTOCOL
    	NSURL *url = [[NSURL alloc] initWithScheme:@"http" host:host path:urlString];
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    	NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    	NSLog(@"Register URL: %@", url);
    	NSLog(@"Return Data: %@", returnData);
    
        /* EasyAPNS End Block */
    
        /* PushNotification Start Block */
        PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];
        [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:devToken];
        /* PushNotification End Block */
    
    #endif
    }
    
    - (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
    {
        PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];
        [pushHandler didFailToRegisterForRemoteNotificationsWithError:error];
    }
    
    - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
    {
        PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];
        NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];
    
        // Get application state for iOS4.x+ devices, otherwise assume active
        UIApplicationState appState = UIApplicationStateActive;
        if ([application respondsToSelector:@selector(applicationState)]) {
            appState = application.applicationState;
        }
    
        [mutableUserInfo setValue:@"0" forKey:@"applicationLaunchNotification"];
        if (appState == UIApplicationStateActive) {
            [mutableUserInfo setValue:@"1" forKey:@"applicationStateActive"];
            [pushHandler didReceiveRemoteNotification:mutableUserInfo];
        } else {
            [mutableUserInfo setValue:@"0" forKey:@"applicationStateActive"];
            [mutableUserInfo setValue:[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] forKey:@"timestamp"];
            [pushHandler.pendingNotifications addObject:mutableUserInfo];
        }
    }
    
    /* STOP BLOCK */
    
    @end

13. Build 在你的 device 上吧, 在emulator 沒辦法測 notification.

14. EasyAPNS Server Side 的部份, 我就沒寫了, 請參考下方 1j2 的連結即可

PS:
1.
這只是我個人的筆記, 寫的有點雜亂 XD
所以請搭配下列 ref 一併服用
未來還會再整理一遍, 目前只是先備忘
2.
產生 .pem
openssl pkcs12 -in Dev_Certificates.p12 -out apns-dev-cert.pem -nodes -clcerts

ref:
http://1j2.com/tutorial-implementing-push-notifications-with-easy-apns/
https://github.com/phonegap/phonegap-plugins/tree/master/iOS/PushNotification
http://www.easyapns.com/apple-delegate

婚戒

今天和三姑姑去Tiffany買了婚戒~~
難得看到三姑姑這麼喜歡~~, 牙一咬,眼一閉就刷了
不過好像應該是我拿著這個戒指和他求婚才對~~怎麼買婚戒的感覺和買衣服一樣…相當的平凡

dspam web ui備忘

1.
若是在dspam 的 web ui retrain 無效的話
記得檢查 /usr/local/bin/dspam 的權限是不是 4555

2.
若是 dspam 的 web ui 出現 500 的話
檢查一下
apache error 看是不是也出現 Premature end of script headers: dspam.cgi
然後檢查一下
dspam web ui 的資料夾是不是放在
/usr/local/www/data/dspam
dspam 預設會放在 /usr/local/www/dspam
但是suexec 是跑 /usr/local/www/data ( 可以下 suexec -V 去檢查路徑 )
所以請將 dspam web ui 放在 /usr/local/www/data 就會動了
要是還是不行的話, 請檢查 /usr/local/lib/perl5/{your_perl_version}/ctime.pl 是不是存在
不在的話就弄一份放到上述位置就行了 ( 主要是因為之前的 dspam.cgi 有 require ctime.pl, 但ctime.pl 忘了在那一版的 perl 已經 deprecated 了. 新的 dspam.cgi 應該不會有這個問題 )

3.
若是發生
Statistical SPAM Protection for 沒有用戶的話
檢查一下 /usr/local/etc/dspam.conf 是不是 644
改成 644 應該就會出現 dspam 了
不然就是 check dspam  db 中的 dspam_stats 這個表
是否有 uid 不在系統中的 ~~ 將他刪掉就行了

4.
再不行的話看一下
/var/log/httpd-suexec.log
確認是什麼問題
如果是
cannot run as forbidden uid
就用 suexec -V 看一下
AP_UID_MIN / AP_GID_MIN ( FreeBSD 預設 1000/1000 )
這兩個 ID 是多少
指定的用戶和組要高於上面兩個值

5.
沒有出現圖的話 ~~
確認一下 perl 的版本
要是 5.22 以上的話
因為 cgi.pm 已經被從 core 移除
所以請裝 /usr/ports/www/p5-CGI.pm
就行了

ref.
http://jianzi0307.blog.163.com/blog/static/20812002007101482635431

安装extmail邮件系统的时候,如果使用/var/www目录,不会出现问题。因为系统默认的apache的suexec的docroot目录是/var/www;
那么,当我们需要修改extmail目录的时候,就要重新编译apache 修改–with-suexec-docroot=/your/path

如果使用自己安装或者lamp套件的时候,例如使用lampp,它suexec的docroot目录是/opt/lampp/htdocs
使用 /opt/lampp/bin/suexec -V 来查看

所以如果修改extmail的目录,还是挺麻烦的:
尽量使用自己编译的apache,编译的时候就要事先指定suexec的docroot目录到你要放置的目录。
而 且还要注意suexec的执行用户,配置虚拟主机SuexecUserGroup的时候,用户和组的UIP和GID要高于suexec默认的 –with-suexec-uidmin –with-suexec-gidmin的UID和GID,不然suexec将被限制使用。

下面摘自张微波的 suexec FAQ
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

关于suexec的FAQ

apache编译支持suexec功能

./configure –enable-suexec –with-suexec-logfile=/www/logs/suexec.log –with-suexec-uidmin=500 –with-suexec-gidmin=100 –with-suexec-caller=daemon –with-suexec-docroot=/www

关于suexec的FAQ
1.出现以下错误  command not in docroot
编译的时候加入–with-suexec-docroot    以后虚拟主机运行suexec的目录必须包含指定的目录里,通过suexec -V 可以察看docroot目录是哪儿

2.出现以下错误  user mismatch (daemon instead of www)
编译的时候加入–with-suexec-caller=daemon  默认是www,但一般apache的运行用户是nobody或者daemon,这里我们是httpd2.2.2,用户是daemon,所以指定这个参数,否则suexec不能被运行。

3.出现以下错误  cannot run as forbidden uid (1001/index.cgi)
在虚拟主机配置SuexecUserGroup时,指定的用户和组,必须高于–with-suexec-uidmin –with-suexec-gidmin 指定的用户uid和gid,否则被限制使用。