Sam Bernard

CakePHP

Cakephp Model-less Form Helper

Posted by Sam Bernard on Fri, Mar 11 2011 23:50:00

Below is a simple helper I've creating to help with generating forms, using field names and options stored in a database. It's actually a modified version of the helper I created for my Cakeforms plugin- an easy to use form plugin for Cakephp/Croogo that basically allows people to create forms for their Croogo site without having to write any code. I'll post about it when I get the time to clean it up and update the git repo.

This helper assumes you are passing an array of fields with the following options:

$formField['FormField'] = array(
    array(
        'name' => 'field_name', //required
        'label' => 'field_label',
        'type' => 'type', //required. Options: fieldset, text, textbox, disabled, textonly, select, checkbox, radio,
        'default' => 'default_value',
        'options' => array('option1' => 'value', //options for checkbox, select and radio
                           'option2' => 'value')
    )

I actually store all these values in a database table- in my model's afterFind() function I convert options from a comma separated list to an associative array.

Below is a basic version of the helper. Put this in your app/views/helpers/ folder and call it cakeform.php. You can download the file here: cakeform.php

class CakeformHelper extends AppHelper {
    public $helpers = array('Html', 'Form', 'Javascript');

/**
 * used in generating form fieldsets
 *
 * @access public
 */
    public $openFieldset = false;

/**
 * Generates form HTML
 *
 * @param array $formData
 * @param mixed $action
 *
 * @return string Form Html
 * @access public
 */
    function insert($formFields, $action){

            $out .= $this->Form->create('Form', array('url' => $action));

            if(isset($formFields['FormField'])){
                foreach($formFields['FormField'] as $field){
                    $out .= $this->field($field);
                }
            }

            if($this->openFieldset == true){
                    $out .= "</fieldset>";
            }

            $out .= $this->Form->end('Submit');

            return $this->output($out);
    }

/**
 * Generates appropriate html per field
 *
 * @param array $field Field to process
 * @parram array $custom_options Custom $form->input options for field
 *
 * @return string field html
 * @access public
 */
    function field($field, $custom_options = array()){
        $options = array();
        $out = '';

        if(!empty($field['type'])){
                switch($field['type']){
                    case 'fieldset':
                        if($this->openFieldset == true){
                                $out .= "</fieldset>";
                        }

                        $out .=  "<fieldset>";
                        $this->openFieldset = true;

                        if(!empty($field['name'])){
                                $out .= "<legend>".Inflector::humanize($field['name'])."</legend>";
                                $out .= $this->Form->hidden('fs_' . $field['name'], array('value' => $field['name']));
                        }
                    break;

                    case 'textonly':
                        $out = $this->Html->para('textonly', $field['label']);
                    break;

                    default:
                        $options['type'] = $field['type'];
                        if(in_array($field['type'], array('select', 'checkbox', 'radio'))){

                                if($field['type'] == 'checkbox'){
                                    if(count($field['options']) > 1){
                                            $options['type'] = 'select';
                                            $options['multiple'] = 'checkbox';
                                            $options['options'] = $field['options'];
                                    } else {
                                        $options['value'] = $field['name'];
                                    }
                                } else {
                                    $options['options'] = $field['options'];
                                    $options['empty'] = 'select one';
                                }

                        }

                        if(!empty($field['depends_on']) && !empty($field['depends_value'])){
                            $options['class'] = 'dependent';
                            $options['dependsOn'] = $field['depends_on'];
                            $options['dependsValue'] = $field['depends_value'];
                        }

                        if(!empty($field['label'])){
                                $options['label'] = $field['label'];

                                if($field['type'] == 'radio'){
                                    $options['legend'] = $field['label'];
                                }
                        }

                        if($field['type'] == 'radio' && count($field['options']) == 2 ){
                            $options['div'] = 'input radio bool';
                            $options['legend'] = false;
                            $options['before'] = $this->Html->div('radio-label', $field['label']);
                        }

                        if(!empty($field['default']) && empty($this->data['Form'][$field['name']])){
                                $options['value'] = $field['default'];
                        }

                        $options = Set::merge($custom_options, $options);
                        $out .= $this->Form->input($field['name'], $options);
                        break;
                }
        }
        return $out;
    }
}

Cached Twitter Profile Widgets in Cakephp

Posted by Sam Bernard on Fri, Mar 11 2011 15:33:00

Configuring the Cache

I use the following cache config in my core.php which will cache the tweets for 15 minutes:

Cache::config('social', array(
        'engine' => 'File',
        'duration'=> 60*15, //15 minutes
));

Retrieving the Tweets

The following pulls the last 5 twitter posts for the user traffick911. It first checks if there is a cached version, and if not it will request the latest tweets from twitter.com. I kept all this in my twitter element, which does break MVC, but it could easily be moved to app_controller.php.

$twitter_response = Cache::read('tweets', 'social');

if(empty($twitter_response)){
  App::import('Core', 'HttpSocket');
  $HttpSocket = new HttpSocket();
  $twitter_response = $HttpSocket->get('http://twitter.com/statuses/user_timeline/traffick911.xml', 'count=5');

  if(strlen($twitter_response)>0){
      Cache::write('tweets', $twitter_response, 'social');  
  }
}

Displaying the Tweets

To actually display the tweets I basically copied the HTML and CSS produced by the twitter widget and converted it into an element:

HTML:

<div class="twtr-doc" style="width: 300px;">
  <div class="twtr-hd"><a target="_blank" href="http://twitter.com/traffick911" class="twtr-profile-img-anchor">
    <img alt="profile" class="twtr-profile-img" src="http://a3.twimg.com/profile_images/544416859/traffick_two_FLAT_edited_normal.jpg" /></a>
    <h3>Traffick911</h3>
    <h4><a target="_blank" href="http://twitter.com/Traffick911">Traffick911</a></h4>
  </div>
  <div class="twtr-bd">
    <div class="twtr-timeline" style="height: auto;">
      <div class="twtr-tweets">
  <?php if(!empty($twitter_response)):
            $tweets=new SimpleXMLElement($twitter_response);?>
        <?php foreach($tweets as $tweet):?>
        <div class="twtr-tweet">
          <div class="twtr-tweet-wrap">
            <div class="twtr-avatar">
              <div class="twtr-img">
                <a target="_blank" href="http://twitter.com/<?php echo $tweet->user->name;?>">
                  <img alt="Traffick911 profile" src="<?php echo $tweet->user->profile_image_url;?>" />
                </a>
              </div>
            </div>
            <div class="twtr-tweet-text">
              <p>
                <a target="_blank" href="http://twitter.com/<?php echo $tweet->user->name;?>" class="twtr-user"><?php echo $tweet->user->screen_name;?></a>
                <?php
                $message=preg_replace("/http:\/\/(.*?)\/[^  ]*/",'\\0',$tweet->text);
                $message=ereg_replace("[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]","<a href=\"\\0\" rel=\"nofollow\">\\0</a>",$message);                  

                echo $message;?>
                <em>            <a target="_blank" class="twtr-timestamp" href="http://twitter.com/<?php echo $tweet->user->name?>/status/<?php echo $tweet->id?>"><?php echo $this->Time->timeAgoInWords(strtotime($tweet->created_at) + $tweet->utc_offset);?></a>
                <a target="_blank" class="twtr-reply" href="http://twitter.com/?status=@<?php echo $tweet->user->name?>%20&amp;in_reply_to_status_id=<?php echo $tweet->id?>&amp;in_reply_to=<?php echo $tweet->user->name?>">reply</a>             </em>
              </p>
            </div>
          </div>
        </div>
        <?php endforeach;?>
  <?php else:?>
         <div class="twtr-tweet">
          <div class="twtr-tweet-wrap">
            <div class="twtr-tweet-text">
              <p>
                Tweets could not be loaded at this time.
              </p>
            </div>
          </div>
        </div> 
  <?php endif;?>
      </div>
    </div>
  </div>
  <div class="twtr-ft">
    <div><a target="_blank" href="http://twitter.com"><img alt="" src="http://widgets.twimg.com/i/widget-logo.png" /></a>
      <span><a target="_blank" class="twtr-join-conv" style="color:#ffffff" href="http://twitter.com/traffick911">Join the conversation</a></span>
    </div>
  </div>
</div>

CSS:

The easiest way to generate the css is to go to http://twitter.com/about/resources/widgets/widget_profile and actually design your widget- then just copy and paste it into your stylesheet.

Timezones in Cakephp 1.3 and php 5.3.

Posted by Sam Bernard on Fri, Mar 11 2011 00:52:00

I recently updated to php version 5.3.5, and immeiately got the following error in my Cakephp 1.3.x apps:

    Warning (2): strtotime() [http://php.net/function.strtotime]: It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/Chicago' for 'CST/-6.0/no DST' instead [CORE/cake/libs/cache.php, line 570]
    Code | Context

    $settings   =   array(
            "engine" => "File",
            "path" => "/private/var/www/skyrocketstudio.com/app/tmp/cache/persistent/",
            "prefix" => "cake_core_",
            "lock" => false,
            "serialize" => true,
            "isWindows" => false,
            "duration" => "+10 seconds",
            "probability" => 100
    )

    strtotime - [internal], line ??
    CacheEngine::init() - CORE/cake/libs/cache.php, line 570
    FileEngine::init() - CORE/cake/libs/cache/file.php, line 81
    Cache::_buildEngine() - CORE/cake/libs/cache.php, line 151
    Cache::config() - CORE/cake/libs/cache.php, line 126
    Configure::__loadBootstrap() - CORE/cake/libs/configure.php, line 420
    Configure::getInstance() - CORE/cake/libs/configure.php, line 52
    include - CORE/cake/bootstrap.php, line 38
    require - APP/webroot/index.php, line 76
    [main] - CORE/index.php, line 55

    Notice: Trying to get property of non-object in /private/var/www/skyrocketstudio.com/cake/libs/cache/file.php on line 248 Fatal error: Call to a member function cd() on a non-object in /private/var/www/skyrocketstudio.com/cake/libs/cache/file.php on line 248 

Turns out it's an easy fix- uncomment date_default_timezone_set('UTC');(around line 242) in your core.php file.

For convenience' sake, here's a list of US timezones, taken from a comment that I always have to refer back to on php.net [http://www.php.net/manual/en/timezones.america.php#93028]

Common Abbr. Value for date_default_timezone_set
AST America/Puerto_Rico
EDT America/New_York
CDT America/Chicago
MDT America/Boise
MST America/Phoenix
PDT America/Los_Angeles
AKDT America/Juneau
HST Pacific/Honolulu
ChST Pacific/Guam
SST Pacific/Samoa
WAKT Pacific/Wake