A web developer's blog. PHP, MySQL, CakePHP, Zend Framework, Wordpress, Code Igniter, Django, Python, CSS, Javascript, jQuery, Knockout.js, and other web development topics.

## Getting the Bayesian Average for rankings (PHP / MySQL)

When you need to correctly display the leader-board based on ratings, you can’t just display the average rating for each entry.

```------------------------------------
Restaurant A | 1 Vote(s) | Rating 10
------------------------------------
Restaurant B | 3 Vote(s) | Rating 6
| Rating 5
| Rating 4
------------------------------------
Restaurant C | 2 Vote(s) | Rating 3
| Rating 10
```

To correctly rank the data above you need to get the Bayesian Average for each restaurant. We have to take into account the number of votes for each restaurant. More votes would push it up the ranking while less votes would have little weight. This means that we if 1 restaurant had 1 vote and with rating of 10, it would not be the number one. (If we just got the average, it would be at the top! – and that would be wrong)

```//In PHP \$avg_num_votes = 2; //The average number of votes for all restaurants (1+3+2)/3 = 2 \$avg_rating = 6.3333; //The average rating for all restaurants (10+6+5+4+3+10)/6 = 6.3333 \$this_num_votes = 3; //The number of votes for current restaurant (Restaurant B) \$this_rating = 5; //The average rating for current restaurant (Restaurant B: (6+5+4)/3 = 5)   \$bayesian_average = ((\$avg_num_votes * \$avg_rating) + (\$this_num_votes * \$this_rating)) / (\$avg_num_votes + \$this_num_votes);```

Using the formula above, we would have the following ratings for each restaurant:

Restaurant A would have 7.55553 = ((2*6.3333) + (1*10)) / (2+1)
Restaurant B would have 5.53332 = ((2*6.3333) + (3*5)) / (2+3)
Restaurant C would have 6.41665 = ((2*6.3333) + (2*6.5)) / (2+2)

A quick solution using MySQL View Tables

```Create View `ratings` AS SELECT restaurant_id, (SELECT count(restaurant_id) FROM ratings) / (SELECT count(DISTINCT restaurant_id) FROM ratings) AS avg_num_votes, (SELECT avg(rating) FROM ratings) AS avg_rating, count(restaurant_id) as this_num_votes, avg(rating) as this_rating FROM ratings GROUP BY restaurant_id```

To get the ratings for the restaurants:

```SELECT restaurant_id, ((avg_num_votes * avg_rating) + (this_num_votes * this_rating)) / (avg_num_votes + this_num_votes) as real_rating FROM `ratings````

Would output something like:

```restaurant_id    real_rating
1                7.555533333333
2                5.533320000000
3                6.416650000000
```

Setup the component like this:

```public \$components = array( 'Facebook' => array( 'appId' => 'xxx', 'secret' => 'xxx', 'cookie' => true, 'fileUpload' => 1, 'canvas' => 1, 'fbconnect' => 1, 'display' => 'page', 'scope' => 'user_about_me,email,publish_actions,publish_stream,photo_upload', 'redirect_uri' => 'https://www.facebook.com/pages/My-Test-Page/12345?id=12345&sk=app_12345' ) );```

Then you should be able to do this in your controllers:

```debug(\$this->userProfile); debug(\$this->loginUrl); debug(\$this->logoutUrl); debug(\$this->hasLiked); debug(\$this->signed_request);```

## Facebook Page Tab: Remove scrollbars and auto-resize height

I have come across many ones, but this one always worked for me without problems.

CSS

`html, body { overflow: hidden; }`

Put this just after the tag:

`<div id="fb-root"></div>`

Put this after you have jQuery and other libraries loaded. Don’t forget to put in your App ID.

```\$(document).ready(function() { window.fbAsyncInit = function() { FB.init({ appId: FB_APP_ID, cookie: true, xfbml: true, oauth: true });   FB.Canvas.setSize({height:600}); setTimeout("FB.Canvas.setAutoGrow()",500);   }; (function() { var e = document.createElement('script'); e.async = true; e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js'; document.getElementById('fb-root').appendChild(e); }()); });```

I forgot where I got this from but if anyone knows, flick me an email!

## Twitter: Get tweets from User Timeline with API 1.1 (PHP)

This code is from http://stackoverflow.com/q/12916539/66767 combined with http://stackoverflow.com/a/15387732/66767 to auto-link URLs and hashtags.

Quick and dirty… copy and paste below!

```<?php function buildBaseString(\$baseURI, \$method, \$params) { \$r = array(); ksort(\$params); foreach(\$params as \$key=>\$value){ \$r[] = "\$key=" . rawurlencode(\$value); } return \$method."&" . rawurlencode(\$baseURI) . '&' . rawurlencode(implode('&', \$r)); }   function buildAuthorizationHeader(\$oauth) { \$r = 'Authorization: OAuth '; \$values = array(); foreach(\$oauth as \$key=>\$value) \$values[] = "\$key=\"" . rawurlencode(\$value) . "\""; \$r .= implode(', ', \$values); return \$r; }   \$url = "https://api.twitter.com/1.1/statuses/user_timeline.json";   \$oauth_access_token = "XXX"; \$oauth_access_token_secret = "XXX"; \$consumer_key = "XXX"; \$consumer_secret = "XXX";   \$oauth = array( 'screen_name' => 'wenbert', 'count' => 2, 'oauth_consumer_key' => \$consumer_key, 'oauth_nonce' => time(), 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_token' => \$oauth_access_token, 'oauth_timestamp' => time(), 'oauth_version' => '1.0');   \$base_info = buildBaseString(\$url, 'GET', \$oauth); \$composite_key = rawurlencode(\$consumer_secret) . '&' . rawurlencode(\$oauth_access_token_secret); \$oauth_signature = base64_encode(hash_hmac('sha1', \$base_info, \$composite_key, true)); \$oauth['oauth_signature'] = \$oauth_signature;   // Make Requests \$header = array(buildAuthorizationHeader(\$oauth), 'Expect:'); \$options = array( CURLOPT_HTTPHEADER => \$header, //CURLOPT_POSTFIELDS => \$postfields, CURLOPT_HEADER => false, CURLOPT_URL => \$url.'?screen_name=wenbert&count=2', CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false);   \$feed = curl_init(); curl_setopt_array(\$feed, \$options); \$json = curl_exec(\$feed); curl_close(\$feed);   \$twitter_data = json_decode(\$json); ?>   <b>Twitter Updates:</b> <div> <?php foreach(\$twitter_data AS \$single_tweet){ //http://stackoverflow.com/a/15387732/66767 \$tweet = \$single_tweet->text; \$tweet = preg_replace("/([\w]+\:\/\/[\w-?&;#~=\.\/\@]+[\w\/])/", "<a target=\"_blank\" href=\"\$1\">\$1</a>", \$tweet); \$tweet = preg_replace("/#([A-Za-z0-9\/\.]*)/", "<a target=\"_new\" href=\"http://twitter.com/search?q=\$1\">#\$1</a>", \$tweet); \$tweet = preg_replace("/@([A-Za-z0-9\/\.]*)/", "<a href=\"http://www.twitter.com/\$1\">@\$1</a>", \$tweet); echo(\$tweet); } ?> </div>```

The configuration:

```<?php // app/Config/core.php Configure::write("App.SITE_URL", 'http://www.mysite.com'); Configure::write("App.UPLOAD_PATH", '/usr/local/www/vhosts/mysite.com/httpdocs/app/webroot/uploads'); //no trailing slash Configure::write("App.UPLOAD_PATH_URL", 'http://mysite/uploads'); //no trailing slash```

The setting up the .json and .txt extensions in the routes file.
I need the txt. This is because Internet Explorer does not accept `'application/json'`.
Without the .txt, IE will send a “Save as” dialog box when the upload is successful.

```// app/Config/routes.php Router::parseExtensions('json', 'txt');```

I have the following for the View:

```<?php // app/View/News/json/fineupload.ctp echo json_encode(\$result);```

And

```<?php // app/View/News/txt/fineupload.ctp echo json_encode(\$result);```

Take note that both are in different directories.

In my controller:

```<?php class NewsController extends AppController { public \$helpers = array('Html', 'Form'); public \$components = array('RequestHandler', 'Security');   public function beforeFilter() { parent::beforeFilter();   if (AuthComponent::user('role') === 'admin' OR AuthComponent::user('role') === 'user') { \$this->Auth->allow('index', 'add', 'edit', 'fineupload'); if(isset(\$this->Security) && (\$this->RequestHandler->isAjax() || \$this->RequestHandler->isPost()) && \$this->action == 'fineupload'){ \$this->Security->validatePost = false; \$this->Security->enabled = false; \$this->Security->csrfCheck = false; } } }   /* --- snipped --- */   public function edit(\$id = null) { \$this->layout = 'admin';   \$this->News->id = \$id;   if(!\$this->News->exists()) { throw new NotFoundException('News not found'); } if(!(\$this->request->is('post') || \$this->request->is('put'))) { \$this->set('id', \$id); \$this->request->data = \$this->News->read(null, \$id); } else { if(\$this->News->save(\$this->request->data)) { //\$this->Session->setFlash( __('The news has been updated'), 'success'); return \$this->redirect(array('action' => 'index')); } else { //\$this->Session->setFlash('The news could not be saved.', 'error'); } } }   /* --- snipped --- */   public function fineupload() { \$this->layout = false;   \$result = array();   // debug(\$_FILES); // array( // [qqfile] => array( // [name] => [share1_glass.gif], // [type] => [image/gif], // [tmp_name] => [C:\wamp\tmp\phpA755.tmp], // [error] => (int) 0, // [size] => (int) 120358 // ) // )   \$temp_file = \$_FILES['qqfile']['tmp_name']; \$target_filename = \$_FILES['qqfile']['name']; \$upload_path = Configure::read("App.UPLOAD_PATH"); \$target_filepath = \$upload_path.'/'.\$target_filename; move_uploaded_file(\$temp_file, \$target_filepath);   \$result['status'] = 'success'; \$result['message'] = 'Upload successful.'; \$result['filename'] = \$target_filename; \$result['webpath'] = Configure::read("App.UPLOAD_PATH_URL").'/'.\$target_filename; \$result['upload_dir'] = Configure::read("App.UPLOAD_PATH_URL");   \$this->set('result', \$result); }```

Then in the HTML part – this is the part where we setup the FineUpload Javascript.

```<!-- Fine Uploader --> <!-- You could load these javascript files the CakePHP way --> <script type="text/javascript" src="/js/fineuploader/header.js"></script> <script type="text/javascript" src="/js/fineuploader/util.js"></script> <script type="text/javascript" src="/js/fineuploader/handler.xhr.js"></script> <script type="text/javascript" src="/js/fineuploader/handler.base.js"></script> <script type="text/javascript" src="/js/fineuploader/handler.form.js"></script> <script type="text/javascript" src="/js/fineuploader/button.js"></script>   <script type="text/javascript" src="/js/fineuploader/uploader.js"></script> <script type="text/javascript" src="/js/fineuploader/uploader.basic.js"></script>   <script> \$(document).ready(function() { \$fub = \$('#fine-uploader-basic'); \$messages = \$('#messages');   //for IE, we would load the mysite.com/news/fineupload.txt //it would return text instead of application/json //if we do not do this, Internet Explorer would display a "Save as" dialog box if(\$.browser.msie) { var request_ext = 'txt'; //http://mysite.com/news/fineupload.txt } else { var request_ext = 'json'; //http://mysite.com/news/fineupload.json } var uploader = new qq.FineUploaderBasic({ button: \$fub[0], multiple: false, request: { endpoint: '<?php echo \$this->webroot ?>news/fineupload.'+request_ext }, validation: { allowedExtensions: ['jpeg', 'jpg', 'gif', 'png', 'pdf'], sizeLimit: 204800 // 200 kB = 200 * 1024 bytes }, callbacks: { onSubmit: function(id, fileName) { \$messages.html('<div id="file-' + id + '" class="alert" style="margin: 20px 0 0"></div>'); }, onUpload: function(id, fileName) { \$('#file-' + id).addClass('alert-info') .html('<img src="<?php echo Configure::read("App.SITE_URL") ?>/loading.gif" alt="Initializing. Please hold."> ' + 'Initializing ' + '“' + fileName + '”'); }, onProgress: function(id, fileName, loaded, total) { if (loaded < total) { progress = Math.round(loaded / total * 100) + '% of ' + Math.round(total / 1024) + ' kB'; \$('#file-' + id).removeClass('alert-info') .html('<img src="<?php echo Configure::read("App.SITE_URL") ?>/loading.gif" alt="In progress. Please hold."> ' + 'Uploading ' + '“' + fileName + '” ' + progress); } else { \$('#file-' + id).addClass('alert-info') .html('<img src="<?php echo Configure::read("App.SITE_URL") ?>/loading.gif" alt="Saving. Please hold."> ' + 'Saving ' + '“' + fileName + '”'); } }, onComplete: function(id, fileName, responseJSON) { console.log('ID: '+id); console.log('fileName: '+fileName); if (responseJSON.status == 'success') { \$('#file-' + id).removeClass('alert-info') .addClass('alert-success') .html('<i class="icon-ok"></i> ' + 'Successfully saved ' + '“' + fileName + '”'); \$('#NewsExternalUrl').val(responseJSON.webpath); } else { \$('#file-' + id).removeClass('alert-info') .addClass('alert-error') .html('<i class="icon-exclamation-sign"></i> ' + 'Error with ' + '“' + fileName + '”: ' + responseJSON.error); } } } }); }); </script> <div id="fine-uploader-basic" class="btn btn-small"> <i class="icon-upload icon"></i> Click to upload a file instead of using the Content textarea below. </div> (pdf, jpg, gif, png files only) <div id="messages"></div>```

## CakePHP: Disabling the Security Component for Specific Actions in a Controller

Sometimes, I need to disable the Security component for certain actions in the controller. For example, if I need to handle FineUpload.

If I do not disable the Security component, I get a “request has been blackholed” error.

```<?php class NewsController extends AppController { public \$helpers = array('Html', 'Form'); public \$components = array('RequestHandler', 'Security');   public function beforeFilter() { parent::beforeFilter();   if (AuthComponent::user('role') === 'admin' OR AuthComponent::user('role') === 'user') { //only allow access to these actions when the role is admin/user \$this->Auth->allow('index', 'add', 'edit', 'delete', 'view', 'fineupload');   //Here, we disable the Security component for Ajax requests and for the "fineupload" action if(isset(\$this->Security) && (\$this->RequestHandler->isAjax() || \$this->RequestHandler->isPost()) && \$this->action == 'fineupload'){ \$this->Security->validatePost = false; \$this->Security->enabled = false; \$this->Security->csrfCheck = false; } } } /*rest of the code*/```
Posted in Uncategorized | 1 Comment

## CakePHP: Select Box / Drop-down List linked from related Model

For example, an `Article` has an `Author`. In the database, the `Article` table would have an `author_id` field. Assuming that we are following the CakePHP Model and Database Conventions (http://book.cakephp.org/2.0/en/getting-started/cakephp-conventions.html#model-and-database-conventions), the models would look something like these:

```<?php ## /Model/Article.php App::uses('AuthComponent', 'Controller/Component');   class Article extends AppModel { public \$name = 'Article';   public \$belongsTo = array( 'Author' => array( 'className' => 'Author', 'foreignKey' => 'author_id' ), ); }```

An `Article` that uses the `articles` table which belongs to an `Author`. The `article` table has a field named `author_id`. The `author_id` is linked to the `id` found in the `author` table.

The Author Model would be:

```<?php ## /Model/Author.php App::uses('AuthComponent', 'Controller/Component');   class Author extends AppModel { public \$name = 'Author';   public \$hasMany = array( 'Article' => array( 'className' => 'Article', 'foreignKey' => 'author_id' ), ); }```

An `Author` uses the `author` table and has many `Article`. The `article` table has an `author_id` to link to it’s `Author`.

Now in one of my controllers, I would have a method that will allow me to add an Article and specify the Author. The Author field would be a dropdown / selectbox.

```<?php ## /Controllers/ArticlesAdminController.php //snip public function add() { \$this->layout = 'admin'; \$this->loadModel('Author'); \$this->loadModel('Article'); \$authors = \$this->Author->find('list'); //we get the authors from the database \$this->set('authors', \$authors);   if (\$this->request->is('post')) { \$this->Article->create(); if (\$this->Article->save(\$this->request->data)) { \$this->Session->setFlash( __('The Article has been saved'), 'success'); return \$this->redirect(array('action' => 'index')); } else { \$this->Session->setFlash(__('The Article could not be saved. Please, try again'), 'error'); //throw new InternalErrorException (__('The user could not be saved. Please, try again')); }   } }```

Finally, we have the view (`/View/ArticlesAdmin/add.ctp`).

```  <h1><?php echo __('Add Article'); ?></h1> <?php echo \$this->Form->create('Article', array('class' => 'well'));   echo \$this->Form->input('title', array( 'class' => 'span5', 'after' => ' <span class="required_indicator">(required)</span> ', 'error' => array('attributes' => array('wrap' => 'span', 'class' => 'label custom-inline-error label-important help-inline')) ));   //the rest of the Article fields here...   //The selectbox / dropdown menu for the Author field echo \$this->Form->input('author_id', array( 'options' => \$authors, 'class' => 'span5', 'error' => array('attributes' => array('wrap' => 'span', 'class' => 'label custom-inline-error label-important help-inline')) ));   ?> <?php echo \$this->Form->end(__('Submit')) ?>```

## CakePHP: Displaying images that are outside the public accessible directory (webroot) using Media Views

Let’s say that `Configure::read('App.uploads_temp')` is somewhere outside the public accessible directory: `/var/www/yourapp/private`

```//This goes into the controller /** * @param \$filename A filename without the path. EG: default-guy.jpg */ public function downloadimage(\$filename) { \$file_parts = pathinfo(\$filename); \$this->viewClass = 'Media'; \$params = array( 'id' => \$filename, 'name' => \$file_parts['basename'], 'download' => false, 'extension' => \$file_parts['extension'], 'path' =>Configure::read('App.uploads_temp'). DS ); \$this->set(\$params); }```

You can view the image through the URL:

` http://yourapp.com/entries/downloadimage/sample-image.jpg`

## Some useful Knockout.JS examples from my JSFiddle

I am learning Knockout.js

Accessing an observable array outside the foreach loop from the HTML
http://jsfiddle.net/wenbert/MEKLN/

Setting a default / selected option in a Select box
http://jsfiddle.net/wenbert/ZpGeS/

Working with child models
http://jsfiddle.net/wenbert/Ev8H6/

Thanks to the guys at Stackoverflow and #javascript in Freenode for replying to my questions. RP Niemeyer from Knockmeout.net, nemesv and tyrsius.

```<?php class UsersController extends AppController {   // ...snip... // the rest of your code here   public function downloadentries() {   if(\$this->request->is('post')) { \$this->loadModel('Member'); \$filename = "myfile.csv"; \$start_date = '2012-10-08 00:00:00'; \$end_date = '2012-10-08 23:59:59'; \$results = \$this->Member->find('all', array( 'conditions' => array('Member.created >=' => \$start_date, 'Member.created <=' => \$end_date) ));   \$csv_file = fopen('php://output', 'w');   header('Content-type: application/csv'); header('Content-Disposition: attachment; filename="'.\$filename.'"');   \$header_row = array("id", "name", "email");   fputcsv(\$csv_file,\$header_row,',','"'); foreach(\$results AS \$result) { \$row = array( \$result['Member']['id'], \$result['Member']['name'], \$result['Member']['email'] );   fputcsv(\$csv_file,\$row,',','"'); } fclose(\$csv_file); } \$this->layout = false; \$this->render(false); return false; }   // ... more code here... }```