This story does not have a happy ending
From Times Online:
A 19-year-old man in Florida committed suicide live on the internet as hundreds of web surfers watched - taunting him and offering encouragement.
Abraham K. Biggs, from Broward County, Florida, announced his intention on an online forum, posted a suicide note on another and then took an overdose of pills in front of his webcam, broadcasting his final moments on Justin.tv.
Mr Biggs lay on his bed motionless for several hours before members of the website became alarmed. With the video still streaming, viewers eventually called the local police, who broke down the door, found the body and switched off the camera. Up to 1,500 people were viewing, according to one report.
A video clip posted on the net shows a police officer entering the room, his handgun drawn, as he checks for any sign of life. Mr Biggs was a member of bodybuilding.com under the name CandyJunkie and was also known under the alias of Feels Like Ecstasy on Justin. tv. He had apparently threatened to commit suicide before.
The last post was about cybermobs emerging in the political fallout of proposition 8. This one at a personal level.
It makes me wonder how we can love and value life through the anonymity that the web gives us.
One of the major things that kids needed to be protected from articulated at the Kids Online conference last week was “themselves” this is a good example of this need.
On the DZone site there's a new tutorial showing how to set up a new PHP project inside of one of the latest versions of the NetBeans IDE using a subversion repository as the base.
This article describes my experience in creating a PHP project in NetBeans IDE 6.5 from the repository version of Mediawiki. The project included 3 kinds of data that a developer would probably want to handle differently. Fortunately, the PHP Project from Existing Sources wizard made it easy to keep these 3 kinds of data separate from each other, as you will see in the following procedure.The tutorial works through a seven-step process showing you how to point the software at your repository and have it pull in the information and automatically set up all the things you'll need (screenshots are included).
On the Debuggable blog Tim Koschutzki has a new post showing how to get CakePHP to play nicely with a HABTM query and pagination.
The problem is that a user inputs some search criteria into a form, the resultset exceeds 30 rows for example and the user must be able to browse through the resultset via different pages. [...] This problem itself is in fact not much of a problem. We just need to store the form conditions somewhere and then hack it together. So what we are going to do is that we raise the difficulty bar a lot more by trying to get the pagination work over a HABTM relation.Code is included for the model and controller to get the job done.
Hey folks,
this post is going to deal with the pretty common problem of paginating a search with the CakePHP framework.
The ProblemThe problem is that a user inputs some search criteria into a form, the resultset exceeds 30 rows for example and the user must be able to browse through the resultset via different pages. The search criteria has to be the same everytime, the form prepopulated with the used search keywords/data and the resultset still has to match the input conditions everytime a new page is clicked.
This problem itself is in fact not much of a problem. We just need to store the form conditions somewhere and then hack it together. So what we are going to do is that we raise the difficulty bar a lot more by trying to get the pagination work over a HABTM relation.
Battlefield BriefingWe agreed we need to store the search criteria somewhere, so we can access it later. No we won't use the DB for that as it is overkill. We will also not use files, since many users may use the search at the same time screwing our hd. :P Yes, we will use the session for that as it is made exactly for these things.
For the example code we are going to use the "Advanced Search" of Flashfun247, a flashgame site which is one of my freetime projects. If you want to see some more of its code, feel free to ask.
We want to search for games based on some input conditions. The Game model is related to the GameCategory model over a HABTM relation, so the same game can be in many categories and a category contains many games. CakePHP's paginator cannot handle pagination over a HABTM so well in its current version. The incident here is that we want every game listed only once in the resultset - and not n times, where n is the number of categories it belongs to.
So we must at some point include a group by statement. However, the paginator will use that group by statement for its internal find('count') call as well, which it does to determine the size of the resultset. This will in fact corrupt the page count screwing us all over. We will see that we can trick the paginator, though. ; )
The ViewTo get us started, let's have a look at the view in /views/searches/advanced.ctp, which is very simple:
php- <h1><?php echo $this->pageTitle = 'Advanced Game Search'; ?></h1>
- <?php
- $html->addCrumb($this->pageTitle);
- echo $form->create('Search', array('action' => 'advanced'));
- if (isset($formData) && !empty($formData)) {
- $form->data = $formData;
- }
- echo $form->input('game_category_id', array('label' => 'Category:', 'options' => $searchCategories, 'empty' => 'All Categories'));
- echo $form->input('keywords', array('label' => 'Text from game name, description or instructions:'));
- echo $form->input('tags', array('label' => 'Is tagged with (separate tags by comma):'));
- $orderOptions = array(
- 'Game.name' => 'Name',
- 'GameCategory.name' => 'Game Category',
- 'Game.avg_rating' => 'Game Rating',
- 'Game.clicks' => 'Number of Plays',
- );
- echo $form->input('order_by', array('label' => 'Order Results By:', 'options' => $orderOptions));
- echo $form->input('order_dir', array('label' => 'Direction:', 'options' => array('asc' => 'Ascending', 'desc' => 'Descending')));
- ?>
- <div class="clear"></div>
- <?php echo $form->end('Search', array('action' => 'search'))?>
- <?php if (isset($games)) : ?>
- <div class="clear"></div>
- <?php if (!empty($games)) : ?>
- <?php echo $this->element('../games/list', array('games' => $games, 'hilite' => $query))?>
- <div class="clear"></div>
- <?php echo $this->element('paging', array('model' => 'GameCategoriesGame'))?>
- <?php else : ?>
- <p class="error-message">Sorry, your search returned no results</p>
- <?php endif; ?>
- <?php endif; ?>
It should be pretty straightforward. The only weird thing here is that $formData array. It is basically the placeholder for our search criteria that the user originally typed into the search form field. The view only needs to know where the form data is and not where it comes from. We simply assign the data to the form helper so it can prepopulate the fields for us (line 7).
The user can input here a substring of the name/description/instructions of a game and he can pick a category where the game must be in. Notice the different order options as well as the string "All Categories" for the empty option of the select tag. One other remarkable thing is that we have both isset($games) and !empty($games) calls there. This is to differentiate if the user has submitted the form already ( isset($games) ) and, if he did, the resultset is not empty which allows us to display that "Nothing found" message.
Here is the /views/games/list.ctp view just so you have the complete code:
php- <?php
- $short = isset($short) ? $short : false;
- $class = $short ? ' short' : '';
- ?>
- <div class="games-list">
- <?php foreach ($games as $game) : ?>
- <div class="game<?php echo $class ?>">
- <div class="game-image">
- <?php echo $this->element('game_image', array('game' => $game, 'thumb' => true))?>
- </div>
- <div class="game-descr">
- <?php
- $name = $game['Game']['name'];
- if (isset($hilite)) {
- $name = $text->highlight($game['Game']['name'], $hilite);
- }
- echo $html->link($name, Game::url($game), null, false, false);
- ?>
- <?php if (!$short) : ?>
- <?php echo $game['Game']['short_desc'] ?>
- <div class="plays"><span>Plays:</span> <?php echo $game['Game']['game_playing_count']?></div>
- <?php endif; ?>
- </div>
- </div>
- <?php endforeach; ?>
- </div>
- ?>
Straightforward... Let's move on to the paging element:
php- <?php
- if (!isset($model) || $paginator->params['paging'][$model]['pageCount'] > 1) : ?>
- <div class="paging">
- <?php echo $paginator->prev('« Previous', array('escape' => false, 'class' => 'prev'), null, array('class'=>'disabled'));?>
- <?php echo $paginator->numbers();?>
- <?php echo $paginator->next('Next »', array('escape' => false, 'class' => 'next'), null, array('class'=>'disabled'));?>
- </div>
- <?php endif; ?>
Notice the different checks at the start in order to figure out if we need to display a div at all.. This is called in the advanced.ctp view and the model GameCategoriesGame is supplied, which is a convenience HABTM model which belongsTo both Game and GameCategory.
The controller actionThe controller action might appear a little big at first glance. However, every line has its purpose. This is in a SearchesController. You could have your own search() method though in about any controller.
php- function advanced() {
- $searchCategories = $this->Game->GameCategory->find('list', compact('conditions'));
- $this->set(compact('searchCategories'));
- $page = 1;
- if (isset($this->params['named']['page'])) {
- $page = $this->params['named']['page'];
- }
- $formData = array();
- $sessionKey = 'advanced_search_query';
- if (isset($this->data['Search']['keywords'])) {
- $formData = $this->data;
- $this->Session->write($sessionKey, $formData);
- } elseif ($this->Session->check($sessionKey)) {
- $formData = $this->Session->read($sessionKey);
- } else {
- Assert::true(false, '404');
- }
- $this->set(compact('formData'));
- if (!empty($formData)) {
- $query = $formData['Search']['keywords'];
- $useQuery = trim(low($query));
- $conditions = array();
- if (!empty($formData['Search']['game_category_id'])) {
- $conditions['GameCategoriesGame.game_category_id'] = $formData['Search']['game_category_id'];
- }
- $conditions = am($conditions, array(
- 'Game.published' => '1',
- 'or' => array(
- 'LOWER(Game.name) LIKE' => "%{$useQuery}%",
- 'LOWER(Game.short_desc) LIKE' => "%{$useQuery}%",
- 'LOWER(Game.long_desc) LIKE' => "%{$useQuery}%",
- 'LOWER(Game.instructions) LIKE' => "%{$useQuery}%"
- )
- ));
- $this->GameCategoriesGame->forcePaginateCount = $this->GameCategoriesGame->paginatorCount(
- 'game_categories_games', $conditions, array('Game')
- );
- $contain = array('GameCategory', 'Game.Tag');
- $order = array('Game.name' => 'asc');
- if (!empty($formData['Search']['order_by'])) {
- $order = array($formData['Search']['order_by'] => $formData['Search']['order_dir']);
- }
- $this->paginate['GameCategoriesGame'] = array(
- 'conditions' => $this->GameCategoriesGame->paginatorConditions('game_categories_games', $conditions),
- 'contain' => $contain,
- 'order' => $order,
- 'limit' => 12
- );
- $games = $this->paginate('GameCategoriesGame');
- $this->set(compact('games', 'query'));
- }
- }
So we are first loading all our game categories to populate the select tag. Then we check if there is a named parameter "page" given. If so, the user clicked on the Previous/Next/Numbered links. If it is not present, we might as well start at page 1. ; ]
Now comes the tricky part. We check if the form was submitted via empty($this->data). If it is submitted, we store all the form data in the session. If the form is not submitted we try to recover the form data from the session. If both the form is not submitted and there is no data in the session, but it still a Get request, something bad happened and we fire the user by asserting the yummyness of his cake.
The rest should be familiar - some processing of the $formData array to extract the proper conditions and order stuff. The most interesting stuff now is that call to $this->GameCategoriesGame->paginatorCount('game_categories_games', $conditions, array('Game'));. This enables us to paginate over the HABTM relation (Game HABTM GameCategory). Here is the code from the GameCategoriesGame model:
php- <?php
- class GameCategoriesGame extends AppModel {
- var $name = 'GameCategoriesGame';
- var $belongsTo = array('GameCategory', 'Game');
- /**
- * Return count for given pagination
- *
- * @param string $paginator Pagination name
- * @param array $conditions Conditions to use
- * @return mixed Count, or false
- * @access public
- */
- function paginatorCount($paginator, $conditions = array(), $contain = array()) {
- $Db = ConnectionManager::getDataSource($this->useDbConfig);
- if (!empty($contain)) {
- $related = ClassRegistry::init($contain[0]);
- }
- $sql = 'SELECT
- COUNT(DISTINCT ' . $this->alias . '.' . $this->belongsTo['Game']['foreignKey'] . ') count
- FROM ' . $Db->fullTableName($this->table) . ' ' . $Db->name($this->alias) . ' ';
- if (!empty($contain)) {
- $sql .= ' INNER JOIN ' . $Db->fullTableName($related->table) . ' ' . $Db->name($related->alias) . ' ';
- }
- $sql .= $Db->conditions($this->paginatorConditions($paginator, $conditions, 'count'));
- $count = $this->query($sql);
- if (!empty($count)) {
- $count = $count[0][0]['count'];
- }
- return $count;
- }
- /**
- * Build conditions for given pagination
- *
- * @param string $paginator Pagination name
- * @param array $extraConditions Extra conditions to use
- * @param string $method 'count', or 'find'
- * @return array Conditions
- * @access public
- */
- function paginatorConditions($paginator, $extraConditions = array(), $method = null) {
- $Db = ConnectionManager::getDataSource($this->useDbConfig);
- $conditions = null;
- if (empty($extraConditions)) {
- $extraConditions = array('1=1');
- }
- switch (strtolower($paginator)) {
- case 'game_categories_games':
- if ($method != 'count') {
- $conditions = array_merge($extraConditions, array('1=1 GROUP BY ' . $this->alias . '.' . $this->belongsTo['Game']['foreignKey']));
- } else {
- $conditions = $extraConditions;
- }
- break;
- }
- return $conditions;
- }
- /**
- * Executed by the paginator to get the count. Overriden to allow
- * forcing a count (through var $forcePaginateCount)
- *
- * @param array $conditions Conditions to use
- * @param int $recursive Recursivity level
- * @return int Count
- * @access public
- */
- function paginateCount($conditions, $recursive) {
- if (isset($this->forcePaginateCount)) {
- $count = $this->forcePaginateCount;
- unset($this->forcePaginateCount);
- } else {
- $count = $this->find('count', compact('conditions', 'recursive'));
- }
- return $count;
- }
- }
- ?>
To make a long story short: You see we build up the count query on our on and then force Cake to use our calculated count via our own forcePaginateCount property of the model. The Group BY is already in there, we can supply extra conditions and have different queries for different types (see the switch statement in paginatorConditions).
Alas, we have to build the sql on our own for the JOINs, which can become a headache for more complex problems. Anyway, this code gives us enough flexibility to build the right pagination for every problem. :) If you can think of a problem this code cannot be used for, please let me know and we discuss.
The paginateCount() method could go into your AppModel, I just put it here to have the code in one place to keep it simpler.
ConclusionThe method presented has some advantages and disadvantages, as always. The advantages would definitely include that we don't have to extend the controller's paginate() method in our app controller. This is what many people do and what I did in the past as well. However, as always, it's not good manners to hack the core.
Another advantage is the flexibility of the code - with just one line, we can calculate pagination counts for almost every occasion, and even if we paginate over two or three HABTM relations (I can show you later).
Disadvantages include some bloat in your models and the need to write sql again (*sigh*), which can become very complex if you have to supply all the JOINS yourself for more complex problems. Apart from that the code does not yet have full integration of the containable behavior. However, that I can add later.
I hope you liked the article and can put it to some use. Credits go to mariano for the original idea for this. If you guys are interested in seeing how I coupled the "Save search" feature from here with all of this, feel free to ask and we can have some nice discussion.
The NETTUTS.com blog has a new screencast posted showing how to create a simple thumbnailing script you can use in any application (like an image gallery).
In this week's screencast, I'll show you how to upload files and then have PHP dynamically create a thumbnail. Whether you're building an ecommerce site, or just a simple gallery, these techniques will absolutely prove to be useful. If you're ready for your "spoonfed" screencast of the week, let's get going!The post also includes all of the code and HTML that you'll need to get it up and running (very cut and paste-able).
PHPBuilder.com has posted a list of resources that they offer to help both beginning and experienced PHP developers to further their knowledge:
PHP is one of the most popular scripting languages used to develop applications on the web today. As a result, internet.com has a multitude of PHP resources throughout our network of websites. Here are some of our best PHP resources, along with some featured tutorials and out-of-network resources that you may not know about.The grouping of links also include external resources like the main PHP site and Zend's website.
- Tillate Blog: Clientside Cache Control
- Community News: O'Reilly Offers PHP/SQL Certificate Series
- Zoe Slattery's Blog: Lateral Thinking
- PHPro.org: SPL Autoload
- Smashing Magazine: 10 Advanced PHP Tips to Improve Your Programming
- Site News: Job Postings for the week of 11.09.2008
- Sebastian Bergmann's Blog: Test Dependencies in PHPUnit 3.4
- Jani Hartikainen's Blog: Improved Zend Framework package maker
- Raphael Stolt's Blog: Rails for PHP Developers book review
- David Otton's Blog: PHP Tip: Classes Aren't Derived From stdClass
I am Canadian so you can probably guess how I would have voted if I could have on Proposition 8 (the California constitutional amendment to define marriage as only between a man and a woman).
My views are not the point of this post. I am very concerned about what is playing out - online and in real life between the two sides of this issues following the passage of the amendment.
First of all we live in a democracy - the people of California voted for it - albeit by a small percentage but that was the will of the people.
When I look at this I think well the way the NO side wins is by doing all the work the YES side did last time - only better. They go and put an amendment to the constitution on the ballot and then build support for it.
The NO campaign assumed it couldn’t loose, was badly organized, didn’t have a comprehensive strategy for building support for its side across diverse communities throughout California. (The YES campaign was on the ground engaging with the black church community for example - they never saw anyone from the NO side come to their communities to engage them on the issue).
As the vote approach the NO side in a final very flawed move started attacking in television adds those who funded the YES side of the proposition and in particular the Mormon Church.
It was this turn of events that has lead into quite disturbing actions and behaviors by the NO campaign post election.
The blacklisting and subsequent public harassment and targeting of specific people and specific religious groups for their beliefs and support of YES on prop 8 is wrong.
I take this personally, I have and do work with people who are Mormon - (When I played water polo in university and in the Identity field). I respect the LDS church and the people in it - they have good values. Their religion is a very American one too (like Christian Science its origins are on this continent). Watch the Frontline/American Experience 4 hour documentary on the history of the church and their experience as a people/religious group.
A close personal family member I know also voted YES and for all I know could have donated.
When mobs start appearing at places of residence of YES contributors and their businesses. It makes me worried.
I thought about this issue earlier in the campaign when I wrote this post There are a lot of donkey’s in my neighborhood (and I know who they are)
because she did about 60 gay ‘activists’ went to her restaurant and strong armed her in a scene reminiscent to Nazi Germany. They went down a list of people who gave as little as 100 dollars to boycott, harrass and attack them. They went there to ‘confront’ her for giving a measley hundred bucks based on her personal faith that she has had since childhood. They argued with her and it was reported by local news reporters was a “heated” confrontation.
So is this the America we want? Where if a private citizen wants to participate in the governmental process that they be harrassed and acosted. Their freedom of speech chilled by thugs.
The artistic director, Scott Eckern, came under fire recently after it became known that he contributed $1,000 to support Proposition 8…
In a statement issued on Wednesday morning, Mr. Eckern said that his donation stemmed from his religious beliefs — he is a Mormon — and that he was “deeply saddened that my personal beliefs and convictions have offended others.”
Phillip Fletcher, a Palo Alto dentist who donated $1,000 to the campaign, is featured prominently on a Web site listing donors targeted for boycott. He said two of his patients already have left over the donation.
This is the site of the Anti Gay Blacklist Then there is a blog called Stop the Mormons.
The night Obama won and there was a party in the main street 6 blocks from my house - I had a moment of insight into the future. This was a happy celebratory Mob - it was basically safe. People were texting their friends and telling them where it was inviting them to join. I Tweeted about it so 900 people knew about it and where it was. I also knew that this new technology of texting and presence based real time information creates an increased capacity for mob formation. It made me wonder about the cultural skills and capacities we need to develop to interrupt mob behavior turning bad.
I think what is going on with the blacklists - that are directly targeting people in their private life is wrong. I think targeting specific religious institutions for protest is wrong.
These people and these religious institutions are not propagating HATE they are just not agreeing that marriage can be between a man and a man or a woman and a woman. This is a cultural difference of opinion.
I “get” where many of the gay activists are coming from - but it is not a place that will get them what they want. Many “fled” to the Bay Area to find a community and place where they could be who they were (gay, lesbian, queer, transgender etc). They were raised in conservative churches in other parts of the country that may have been explicitly anti-gay. They likely have strong feelings against these institutions and similar ones. It does not make it OK to the hate these people and act out against them. (If they want to proactively work on cultural change within these communities - Soul Force is doing a good job using nonviolence to work on change.)
We in the identity community need to understand what has unfolded here. The No on Prop 8 groups are using publicly available information. However this used to be information you could get if you went and asked for the paper versions from the court house. So it was public but with high friction to get the information. The web lowers the cost of getting this information (close) to zero - Daniel Solove writes about the change in publicly available information in the Digital Person.
I wonder about how we can balance the need to know who has contributed to political campaigns and propositions while at the same time prevent harassment and the emergence of negative physical and cyber mobs.




