cchost
[ class tree: cchost ] [ index: cchost ] [ all elements ]
ccHost - Developer Cookbook

ccHost - Developer Cookbook

A (hopefully) simple breakdown of common operations.

This documenation refers to ccHost 4.5. There are serious changes coming in 5.0 Make sure to keep tabs at the ccHost Wiki

Table of Contents

Read Me First

This document is a continuation of the ccHost Administrators Guide. If nothing else you need to familiarize yourself with Customizing Files and Using the ccHost Query/Formatting Engine. If you need to do anything more than the most rudimentary XHTML output then you also need know the material in XHTML in ccHost and Tutorial: Create a New Skin

While it is tempting to dig around in ccHost code to cut/paste out pieces there are many places that today would be considered, er, less than 'best practices.' By reading this document you can at least begin to recognize what is supposed to be happening in some of the 'older' code.

Directory Structure

Each ccHost installation has a set of system directories that start with cc prefix. You should only ever touch the cc* directories in dire emergencies.

Your installation has a set of directories where the system will look for custom code, XHTML templates, log files, temp files, etc. Before starting work on customizing your site, go to Global Settings then Paths and take a look around. When this document refers to your viewfile Path, Plugins Path, etc. it means the values in the Paths admin screen (aka user directories).

How files are found:

  1. When looking for specific files, the default behavoir is that user directories are searched in the order specified in the Paths admin screen. The first matching filename will be used. Subdirectories are not searched with one exception mentioned below.
  2. Any file with a .php extension (aka module) anywhere in the Plugins Path will be loaded each and every page (or AJAX) request. The ordre of directories in Plugins Path will be honored, but actual the actual order the files are loaded within each directory is system dependent so you should not assume an order.

    Running code at the top of the module is officially a Bad Idea. You should wait for the CC_EVENT_APP_INIT event to occur before even thinking about executing custom code.

  3. The .php files in the system directory ccextras will is loaded first, then the Plugins Path user directores.
  4. The viewfile (alias docs) command uses viewfile Paths to search for the requested document. If the requested file is not found, the system will try again with an .xml extension (assuming the request had no extension). So the following requests will all yeild the same result:
    http://your_install/media/docs/query              
    http://your_install/media/docs/query.xml
    http://your_install/media/viewfile/query
    http://your_install/media/viewfile/query.xml
  5. When looking for a template from PHP code the Skins Path user directories are searched in the order specified by the user. However, when a template is referenced from within another template, the directory of the calling template will be searched first, then, if not found, the user directories.
  6. For the template (t) parameter of a Query request, the Skins Path is searched in order, adding a .xml extension if needed. If not found an additional search of subdirectories specifically called "formats" will be used.

Develepment Environment

Enabling Debug Mode

In order to enable debugging on a development non-production machine, put the following code into a module in your Plugins Path:

  1. <? CCDebug::Enable(true); ?>

Inspecting Variables

With debugging enabled, you can inspect any variable on the screen using CCDebug::PrintVar or in the log using CCDebug::LogVar or send any message to the log using CCDebug::Log.

CCDebug::PrintVar will stop execution at the point it is called and display the variable in the browser. You can not pass a constant, it must be a variable (or object or array, etc.) that can be a PHP reference.

HINT: If you want to inspect more than one variable at a time, put them into an array.

  1. // Some examples of
  2. function foo()
  3. {
  4. CCDebug::PrintVar($_POST);
  5. }
  6.  
  7. function bar()
  8. {
  9. global $CC_GLOBALS;
  10.  
  11. CCDebug::PrintVar($CC_GLOBALS);
  12. }
  13.  
  14. function some_method_1($arg1, $arg2, $arg3 )
  15. {
  16. // this will execute in any mode
  17. $value1 = $arg1 + 10;
  18.  
  19. // this will only work in debug mode
  20. CCDebug::PrintVar($value1);
  21.  
  22. // this will not execute in debug mode
  23. $value2 = $arg2 - 10;
  24. }
  25.  
  26. function some_method_2($arg1, $arg2, $arg3 )
  27. {
  28. $x[] = $arg1;
  29. $x[] = $arg2;
  30. CCDebug::PrintVar($x);
  31. }
  32.  
  33. function some_method_2($arg1 )
  34. {
  35. // This won't work, the var must be a reference
  36. CCDebug::PrintVar('some arg: ' . $arg1);
  37.  
  38. // this is fine
  39. $x = 'some arg: ' . $arg1;
  40. CCDebug::PrintVar($x);
  41. }
  42.  
  43. function called_from_template($arg1)
  44. {
  45. // if you are debugging code that is part of the
  46. // template display or called from a PHPTAL
  47. // template then you must specify
  48. // that the call is "not template safe"
  49.  
  50. CCDebug::PrintVar($arg1, false);
  51. }

CCDebug::LogVar and CCDebug::Log will continue execution passed the point they are called and put the result into the log. (The log file is called cc-log.txt in Logfile Directory.)

  1. function some_method_1($arg1, $arg2, $arg3 )
  2. {
  3. // code will execute
  4. $value1 = $arg1 + 10;
  5.  
  6. CCDebug::LogVar('this is the value:', $value1);
  7.  
  8. // so will this
  9. $value2 = $arg2 - 10;
  10. }
  11.  
  12. function some_method_2($arg1, $arg2, $arg3 )
  13. {
  14. // code will execute
  15. $value1 = $arg1 + 10;
  16.  
  17. CCDebug::Log("I'm here at line " . __LINE__ );
  18.  
  19. // so will this
  20. $value2 = $arg2 - 10;
  21. }

Displaying a Stack Trace

There's a lot of common code in ccHost and sometimes it's difficult to figure out who is calling what in what context. If you're seeing an error in common code make sure to (turn on debug mode) and insert the following line where the error is occurring:


Create a Quick Test Bed

Sometimes you just want to see if your idea will work at all. To create a quick and dirty test bed start by creating a module with this code.

  1. CCEvents::AddHandler(CC_EVENT_APP_INIT, 'just_testing' );
  2.  
  3. function just_testing()
  4. {
  5. if( empty($_GET['test']) || !CCUser::IsAdmin()))
  6. return;
  7.  
  8. // your code here....
  9.  
  10. }

To invoke your test bed browse to: http://your_cchost_install/?test=1


Code Modules

Create a New Module

Create any file with .php extension and put it into Plugins Path. You're done.


Create a URL and Bind it to a Method

  1. // Tell ccHost you have a URL to map:
  2. CCEvents::AddHandler( CC_EVENT_MAP_URLS,
  3. array( 'myclass', 'OnMapUrls' ) );
  4.  
  5. class myclass
  6. {
  7. function OnMapUrls()
  8. {
  9.  
  10. // this will be called when ccHost needs to know about
  11. // url mappings, it is only called when an admin does
  12. // updates with ?update=1
  13. //
  14. CCEvents::MapUrl( ccp('some', 'url'),
  15. array( 'myclass', 'some_url_handler') ,
  16. CC_MUST_BE_LOGGED_IN
  17. );
  18. }
  19.  
  20. // this is your custom code that will be called
  21. // when someone browses to some/url
  22. //
  23. function some_url_handler($required, $optional='')
  24. {
  25. // some random code:
  26. CCPage::SetTitle('Hello $required!');
  27. if( empty($optional) )
  28. CCPage::Prompt("hello world!");
  29. else
  30. CCPage::Prompt("hello $optional!");
  31. }}

The last parameter is an access flag that must be one of the following:

To register the new binding browse to: http://example.com/?update=1. To see this action you can call any of the following urls:

http://example.com/media/some/url/cchost
http://example.com/media/some/url/cchost/mom
http://example.com/media/some/url/goodbye/marvin

Database

Create a New Database Column

Here are the steps to add a new database column into an existing ccHost table.

First: create a file whose name is in the format: update_unique_part.inc and put that into ccextras directory.

For the unique_part you should use something that identifies you and a version number:

  1. ccextras/update_joesoft_v_1_0.inc
  2. ccextras/update_janeplugins_v_3_2a.inc

In that module create a class derived from CCUpdate and implement the Update method that is named exactly the same name as the unique_part of the file name:

  1. // columns required by joesoft
  2.  
  3. class joesoft_v_1_0 extends CCUpdate
  4. {
  5. function Update()
  6. {
  7. }
  8. }

Next you want to call the inherited _check_for_field() method. The prototype is:

  1. function _check_for_field( $table_name,
  2. $column_name,
  3. $column_description )

Let's say you want to add an column for 'age' to the users table:

  1. // columns required by joesoft
  2.  
  3. class joesoft_v_1_0 extends CCUpdate
  4. {
  5. function Update()
  6. {
  7. $this->_check_for_field(
  8. 'cc_tbl_user',
  9. 'user_age',
  10. 'INT(4) unsigned');
  11. }
  12. }

When someone installs your plugin they will have to (logged in as admin) update their site by browsing to: http://cchost_installation/?update=1


Create a New Database Table

To add an entirely new table to a ccHost installation follow the same steps to Create a New Database Column (Create a New Database Column) except for the code in Update which will look something like this:

  1. // table required by joesoft
  2.  
  3. class joesoft_v_1_0 extends CCUpdate
  4. {
  5. function Update()
  6. {
  7. $sql = '
  8. CREATE TABLE joes_table
  9. (
  10. jt_id int(11) unsigned NOT NULL auto_increment,
  11. jt_user int(11) unsigned NOT NULL,
  12. jt_age int(4) unsigned NOT NULL,
  13. jt_allow_im int(2) unsigned NOT NULL,
  14. jt_im varchar(4) NOT NULL,
  15. jt_location varchar(255) NOT NULL,
  16.  
  17. PRIMARY KEY jt_id (jt_id)
  18. )
  19. ';
  20.  
  21. CCDatabase::Query($sql);
  22. }
  23. }

It is strongly suggested that you create a ccHost table wrapper for the class. After you create a new module (Create a New Module) you should create a class derived from CCTable:

  1. class JSJoesTable extends CCTable
  2. {
  3. function JSJoesTable()
  4. {
  5. $this->CCTable( 'joes_table', // mysql table name
  6. 'jt_id' // PRIMARY KEY
  7. );
  8.  
  9. }
  10.  
  11. // create a singleton factory
  12. function & GetTable()
  13. {
  14. static $_table;
  15. if( empty($_table) )
  16. $_table = new JSJoesTable();
  17. return $_table;
  18. }
  19. }

Insert a Record Into a Table

All tables in the system follow the same code pattern for row insertion:

  1. function AddUserAge($user_id, $age)
  2. {
  3. $joestable =& JSJoesTable::GetTable();
  4. $args['jt_user'] = $user_id;
  5. $args['jt_age'] = $age;
  6. $joestable->Insert($args);
  7. }

If you need to know the key of the new record use NextID:

  1. function AddUserAge($user_id, $age)
  2. {
  3. $joestable =& JSJoesTable::GetTable();
  4. $args['jt_id'] = $joestable->NextID();
  5. $args['jt_user'] = $user_id;
  6. $args['jt_age'] = $age;
  7. $joestable->Insert($args);
  8. return $args['jt_id'];
  9. }

Update a Database Record

All tables in the system follow the same code pattern for row updating. The Update assumes that the argument array contains a primary key (aka 'id') value:

  1. function UpdateAge($jt_id, $age)
  2. {
  3. $joestable =& JSJoesTable::GetTable();
  4. $args['jt_id'] = $jt_id;
  5. $args['jt_age'] = $age;
  6. $joestable->Update($args);
  7. }

If you don't have the key you'll need to look it up:

  1. function UpdateUserAge($user_id, $age)
  2. {
  3. $joestable =& JSJoesTable::GetTable();
  4. $where['jt_user'] = $user_id;
  5. $jt_id = $joestable->QueryKey($where);
  6. UpdateAge($jt_id, $age);
  7. }
  8.  
  9. function UpdateAge($jt_id, $age)
  10. {
  11. $joestable =& JSJoesTable::GetTable();
  12. $args['jt_id'] = $jt_id;
  13. $args['jt_age'] = $age;
  14. $joestable->Update($args);
  15. }

Adding Custom Data To Upload Records

You can add custom data to any upload record without changing the database meta data (adding tables or columns). The upload_extra field in each upload record ("row" vs. "record") designed specifically to hold this kind of custom data. There is also a simple API for setting and getting your data.

To Add Your Custom Data
  1. function add_my_data($my_data,$upload_id)
  2. {
  3. $uploads =& CCUploads::GetTable();
  4. $uploads->SetExtraField(
  5. $upload_id, // This can also be a reference
  6. // to a record
  7. 'joes_extra', // The name of your extra field
  8. $my_data // This can be any serializeable type
  9. // of data including array.
  10. );
  11. }
To Retrieve Your Custom Data
  1. function get_my_data($upload_id)
  2. {
  3. $uploads =& CCUploads::GetTable();
  4. return $uploads->GetExtraField($upload_id, 'joes_extra');
  5. }

Alternatively, if you are already looking at a record, you can access the data directly:

  1. function get_my_data_from_record(&$record)
  2. {
  3. if( exist( $record['upload_extra']['joes_extra'] ) )
  4. return $record['upload_extra']['joes_extra'];
  5. return null;
  6. }
To Search For Your Custom Data

The only drawback to using this method is that searching for your data is extremely expensive (it takes a long time) and is strongly recommended you don't do it. See the documentation for CCUploads::WhereForSerializedField if you really must or if searching on this data is critical then you should probably consider adding a new column (Create a New Database Column) to cc_tbl_uploads or even better, a new table (Create a New Database Table) instead using the extra field.

If you can get by with just searching for records with presence of your extra data then you can add a system tag to the upload row when you add the data:

  1. function add_my_data($my_data,$upload_id)
  2. {
  3. $uploads =& CCUploads::GetTable();
  4. $uploads->SetExtraField($upload_id,
  5. 'joes_extra',
  6. $my_data
  7. );
  8.  
  9. // A terrible name for a useful function:
  10. // this call will add a tag 'joes_data'
  11. CCUploadAPI::UpdateCCUD($upload_id,'joes_tag','');
  12.  
  13. }

Now it's just a matter of setting a tag filter to limit results that have your custom data in the extra fields:

  1. function list_recs_with_joes_data()
  2. {
  3. $uploads =& CCUploads::GetTable();
  4.  
  5. // Filter results with this tag
  6. $uploads->SetTagFilter('joes_tag');
  7. // Empty 'where' gets all tagged records
  8. $records =& $uploads->GetRecords('');
  9. // Don't forget to release the tag filter (!!!)
  10. $uploads->SetTagFilter('');
  11. // $records now points to all uploads with our data
  12. $count = count($records);
  13. for( $i = 0; $i < $count; $i++)
  14. {
  15. $record =& $records[$i];
  16. $joes_data = $record['upload_extra']['joes_data'];
  17. //....
Editing and Deleting Your Custom Data

Editing and deleting the data has some non-obvious implications whether you use the tagging method above or not. This method puts some of the basics together:

  1. function update_extra_data($upload_id, $my_data)
  2. {
  3. $uploads =& CCUploads::GetTable();
  4.  
  5. // let's see what's currently there:
  6.  
  7. $current_data = $uploads->GetExtraField($upload_id,'joes_data');
  8.  
  9. if( empty($my_data) )
  10. {
  11. // delete the tag:
  12.  
  13. CCUploadAPI::UpdateCCUD($upload_id,'','joes_tag');
  14.  
  15. if( !empty($current_data) )
  16. {
  17. // something is there now, let's clear it:
  18.  
  19. $uploads->SetExtraField($upload_id,'joes_data','');
  20. }}
  21. else
  22. {
  23. // do we need to add a tag?
  24.  
  25. if( empty($current_data) )
  26. CCUploadAPI::UpdateCCUD($upload_id,'joes_tag',''); // yup
  27.  
  28. // set the extra data
  29. $uploads->SetExtraField($upload_id,'joes_data',$my_data);
  30.  
  31. }
  32. }

Forms

Display a Form

Create a class that derives that from CCForm. The name of the class must in this specific form:

  1. 2-letter-prefix form_name Form (no spaces)
  2.  
  3. class CCJoesEditForm extends CCForm // this is ok
  4. class xxEditAgeForm extends CCForm // this is ok
  5.  
  6. class Joe extends CCForm // this is not
  7. class xxJoe extends CCForm

Fields are created in the constructor of the form object:

  1. class JSJoesEditForm extends CCForm
  2. {
  3. function JSJoesEditForm()
  4. {
  5. $this->CCForm();
  6.  
  7. $fields = array(
  8. 'age' => array(
  9. 'label' => 'Age',
  10. 'form_tip' => 'Enter your age here',
  11. 'formatter' => 'textedit',
  12. 'flags' => CCFF_REQUIRED,
  13. ),
  14. 'allow_im' => array(
  15. 'label' => 'Enable Instant Messaging',
  16. 'form_tip' => 'Enable instant messages notifications',
  17. 'formatter' => 'checkbox',
  18. 'flags' => CCFF_NONE,
  19. ),
  20. 'im' => array(
  21. 'label' => 'IM Service',
  22. 'formatter' => 'select',
  23. 'options' => array(
  24. 'aol' => 'Americal Online',
  25. 'msn' => 'Microsoft',
  26. 'irc' => 'Independent Relay Crisis'
  27. ),
  28. 'flags' => CCFF_NONE,
  29. ),
  30. );
  31.  
  32. $this->AddFormFields($fields);
  33. }
  34. }

A 'formatter' is the thing that generates the html for the field and validates the value during submit. There are many standard and built in generators. See cclib/cc-form.php for a list functions that start with generator_ for the standard ones.

The 'flags' field is very important and can lead to a lot of confusion if used incorrectly. See the documentation AddFormFields in cclib/cc-form.php for what is possible. All flags begin with CCFF_ prefix.

To display your form:

  1. function EditJSInfo()
  2. {
  3. CCPage::SetTitle('Edit Special User Info');
  4.  
  5. $form = new JSJoesEditForm();
  6.  
  7. CCPage::AddForm( $form->GenerateForm() );
  8. }

Populate a Form

There are several ways to populate a form you've created (Display a Form). By far the most efficient way is to use PopulateValues. In order for PopulateValues to work you first have to mark the fields you expect to populate with CCFF_POPULATE flag

  1. 'age' => array(
  2. 'label' => 'Age',
  3. 'form_tip' => 'Enter your age here',
  4. 'formatter' => 'textedit',
  5. 'flags' => CCFF_REQUIRED | CCFF_POPULATE,
  6. ),
  7. 'allow_im' => array(
  8. 'label' => 'Enable Instant Messagings',
  9. 'formatter' => 'checkbox',
  10. 'flags' => CCFF_POPULATE,
  11. ),
  12. 'im' => array(
  13. 'label' => 'IM Service',
  14. 'formatter' => 'select',
  15. 'options' => array(
  16. 'aol' => 'Americal Online',
  17. 'msn' => 'Microsoft',
  18. 'irc' => 'Independent Relay Crisis'
  19. ),
  20. 'flags' => CCFF_POPULATE,
  21. ),

Now you're ready to populate:

  1. function EditJSInfo($age, $allow_im, $im )
  2. {
  3. CCPage::SetTitle('Edit Special User Info');
  4.  
  5. $form = new JSJoesEditForm();
  6.  
  7. $values['age'] = $age;
  8. $values['allow_im'] = $allow_im;
  9. $values['im'] = $im;
  10. $form->PopulateValues($values);
  11.  
  12. CCPage::AddForm( $form->GenerateForm() );
  13. }

HINT: If you are populating values from a database row (Create a New Database Table), then your life will get much simpler if name the fields the same as the database columns:

  1. class JSJoesEditForm extends CCForm
  2. {
  3. function JSJoesEditForm()
  4. {
  5. $this->CCForm();
  6.  
  7. $fields = array(
  8. 'jt_age' => array(
  9. 'label' => 'Age',
  10. 'form_tip' => 'Enter your age here',
  11. 'formatter' => 'textedit',
  12. 'flags' => CCFF_REQUIRED | CCFF_POPULATE,
  13. ),
  14. 'jt_allow_im' => array(
  15. 'label' => 'Enable Instant Messagings',
  16. 'formatter' => 'checkbox',
  17. 'flags' => CCFF_POPULATE,
  18. ),
  19. 'jt_im' => array(
  20. 'label' => 'IM Service',
  21. 'formatter' => 'select',
  22. 'options' => array(
  23. 'aol' => 'Americal Online',
  24. 'msn' => 'Microsoft',
  25. 'irc' => 'Independent Relay Crisis'
  26. ),
  27. 'flags' => CCFF_POPULATE,
  28. ),
  29. );
  30.  
  31. $this->AddFormFields($fields);
  32. }
  33. }

Now the results of doing a query for a row can be used directly to populate the form:

  1. function EditJSInfo($jt_id )
  2. {
  3. CCPage::SetTitle('Edit Special User Info');
  4.  
  5. $form = new JSJoesEditForm();
  6.  
  7. $joestable =& JSJoesTable::GetTable();
  8. $values = $joestable->QueryKeyRow($jt_id);
  9. $form->PopulateValues($values);
  10.  
  11. CCPage::AddForm( $form->GenerateForm() );
  12. }

Handling Form Submit

To handle a user submit of a form you've created (Display a Form) we'll use the same method we use to populate the form (Populate a Form), only now we'll check if we are processing the submit.

You can use the same URL to display the form initially and handle the submit by mapping one URL (Create a URL and Bind it to a Method) to this method.

For (marginal) security reasons, the name of the form in lower case is in PHP's $_POST array so you want to check for that to see if you are in fact, process the submit:

  1. function EditJSInfo($jt_id )
  2. {
  3. CCPage::SetTitle('Edit Special User Info');
  4.  
  5. $form = new JSJoesEditForm();
  6.  
  7. if( empty($_POST['joesedit']) )
  8. {
  9. // this is first time we are displaying the form
  10.  
  11. $show_form = true;
  12. }
  13. else
  14. {
  15. // we are in submit...
  16.  
  17. $show_form = false;
  18. }
  19.  
  20. if( $show_form )
  21. {
  22. // populate the form from a database
  23.  
  24. $joestable =& JSJoesTable::GetTable();
  25. $values = $joestable->QueryKeyRow($jt_id);
  26. $form->PopulateValues($values);
  27.  
  28. CCPage::AddForm( $form->GenerateForm() );
  29. }
  30. }

You'll want to validate the fields first before you get the values. If the form did not validate then ccHost automatically re-populates the data from the user's input and marks the fields that did not validate, but you have to redisplay the form. Here's what all that looks like:

  1. function EditJSInfo($jt_id )
  2. {
  3. CCPage::SetTitle('Edit Special User Info');
  4.  
  5. $form = new JSJoesEditForm();
  6.  
  7. if( empty($_POST['joesedit']) )
  8. {
  9. // this is first time we are displaying the form
  10.  
  11. $show_form = true;
  12.  
  13. // get the values from the database
  14.  
  15. $need_values = true;
  16. }
  17. else
  18. {
  19. // we are in submit...
  20.  
  21. if( $form->ValidateFields() )
  22. {
  23. // great, let's get the user's inputs...
  24.  
  25. $form->GetFormValues($values);
  26.  
  27. // process user values here...
  28.  
  29. $joestable =& JSJoesTable::GetTable();
  30. $values['jt_id'] = $jt_id; // Update requires the key
  31. $joestable->Update($values);
  32.  
  33. CCPage::Prompt("Your special information has been saved");
  34.  
  35. $show_form = false;
  36. }
  37. else
  38. {
  39. // wups, have to show the form again
  40. // ccHost will display all the errors automatically
  41.  
  42. $show_form = true;
  43.  
  44. // do not populate the form from the database
  45.  
  46. $need_values = false;
  47.  
  48. }}
  49.  
  50. if( $show_form )
  51. {
  52. if( $need_values )
  53. {
  54. // populate the form from a database
  55.  
  56. $joestable =& JSJoesTable::GetTable();
  57. $values = $joestable->QueryKeyRow($jt_id);
  58. $form->PopulateValues($values);
  59. }
  60.  
  61. CCPage::AddForm( $form->GenerateForm() );
  62. }
  63. }

NOTE: The GetFormValues method won't work at all until after you call ValidateFields so you always have to call both to get the user values.


Dynamically Insert a Form Field

You can dynamically add a field to an existing form (e.g. the user profile or one of the upload forms). In order to do this you need to have an understanding of how forms work in general (Display a Form).

Create a new module (Create a New Module) and put the following lines at the top:

  1. CCEvents::AddHandler(CC_EVENT_FORM_FIELDS, array( 'myclass', 'OnFormFields'));
  2. CCEvents::AddHandler(CC_EVENT_FORM_POPULATE, array( 'myclass', 'OnFormPopulate') );
  3. CCEvents::AddHandler(CC_EVENT_FORM_VERIFY, array( 'myclass', 'OnFormVerify') );

Now create a class with those methods

  1. class myclass
  2. {
  3. function OnFormFields(&$form,&$fields)
  4. {
  5. }
  6.  
  7. function OnFormPopulate(&$form,&$values)
  8. {
  9. }
  10.  
  11. function OnFormVerify(&$form,&$isvalid)
  12. {
  13. }
  14. }

Since your code will be called whenever a form is displayed you'll have to check to see if it's the form you care about (in this case the form used to edit user profile):

  1. class myclass
  2. {
  3. function OnFormFields(&$form,&$fields)
  4. {
  5. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  6. return;
  7. }
  8.  
  9. function OnFormPopulate(&$form,&$values)
  10. {
  11. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  12. return;
  13. }
  14.  
  15.  
  16. function OnFormVerify(&$form,&$isvalid)
  17. {
  18. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  19. return;
  20. }
  21. }

For each handler you can add the code required

  1. class myclass
  2. {
  3. // called when form object is being constructed
  4.  
  5. function OnFormFields(&$form,&$fields)
  6. {
  7. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  8. return;
  9.  
  10. // add our field into the form:
  11.  
  12. $fields['jt_location'] =
  13. array( 'label' => 'Location',
  14. 'form_tip' => 'Where are you?',
  15. 'formatter' => 'textedit',
  16. 'flags' => CCFF_NONE);
  17. }
  18.  
  19.  
  20. // called when form object is being displayed
  21. // for the first time
  22.  
  23. function OnFormPopulate(&$form,&$values)
  24. {
  25. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  26. return;
  27.  
  28. // do what you have to translate what's in $values
  29. // to a value the user expects...
  30.  
  31. // look up the user's info in our table
  32.  
  33. $joestable =& new JSJoesTable::GetTable();
  34. $where['jt_user'] = $values['user_id'];
  35. $location = $joestable->QueryItem('jt_location', $where);
  36.  
  37. // now, set the value into the form:
  38.  
  39. $form->SetFormValue('jt_location',$location);
  40. }
  41.  
  42.  
  43. called on form submit after the rest of the
  44. // form has validated (or not)
  45.  
  46.  
  47. function OnFormVerify(&$form,&$isvalid)
  48. {
  49. if( strtolower( get_class($form) ) != 'ccuserprofileform' )
  50. return;
  51.  
  52. // this is the safe way to get a value from
  53. // the submitted form:
  54.  
  55. $location = CCUtil::StripText($_POST['jt_location']);
  56.  
  57. // do what you have to validate the data
  58.  
  59. $isvalid_location = my_check_location_func($location);
  60.  
  61. if( $isvalid_location )
  62. {
  63. // our data validated
  64.  
  65. if( $isvalid )
  66. {
  67. // so did the rest of the form, let's save
  68. // our location data
  69.  
  70. // get the user id from the form:
  71.  
  72. $user_id = $form->GetFormValue( 'user_id' );
  73.  
  74. // get an instance of our table
  75.  
  76. $joestable =& new JSJoesTable::GetTable();
  77.  
  78. // get the key for this user
  79. // so we can do the update
  80.  
  81. $args['jt_user'] = $user_id;
  82. $args['jt_id'] = $joestable->QueryKey($args);
  83.  
  84. // save the data to the table
  85.  
  86. $args['jt_location'] = $location;
  87.  
  88. $joestable->Update($args);
  89. }}
  90. else
  91. {
  92. // our data did not validate,
  93. // tell the user why:
  94.  
  95. $form->SetFieldError('jt_location',
  96. 'The location must be on Earth');
  97. }
  98.  
  99. $isvalid |= $isvalid_location;
  100. }
  101. }

X/HTML

If you are totally unfamiliar with PHPTAL you will likely get pretty frustrated pretty fast.

Display HTML (Squirt)

Bind a URL to a method and you can squirt directly into the page:

  1. function just_show_it()
  2. {
  3. $html ='
  4. <h3>Some hacked in HTML</h3>
  5. <p>This will be in the client area.</p>
  6. ';
  7.  
  8. CCPage::PageArg('body_html',$html,'show_body_html');
  9. }

Display HTML (viewfile)

Put an XML file into the viewfile Path and you can invoke php code from within it. (See the admin Query tutorial for an example.) The advantage of this method is that you don't have to bind a new URL to see the results, just use the viewfile (a.k.a. docs) command. The disadvantage is that this method is totally unsuitable for even mildly complex code.


Display HTML (Skin Macro)

Bind a URL to a method and invoke a macro that is known to the current skin:

  1. function show_my_files($extra_tags='')
  2. {
  3. if( !empty($extra_tags) )
  4. $extra_tags = ", $extra_tags";
  5.  
  6. // retrieve the records...
  7. //
  8. require_once('cclib/cc-query.php');
  9. $query = new CCQuery();
  10. list( $records ) = $query->Query(array('f'=>'php','tags'=>"remix$exrra_tags"));
  11.  
  12. // munge it with your special touch...
  13. //
  14. for( $i = 0; $i < count($records);
  15. {
  16. // do some custom special code with $record[$i] =
  17. }
  18.  
  19. // parameters:
  20. // 'records' -- this is the name of variable used in the template
  21. // the template expects this to be a specific thing
  22. // so you can't just make up a name
  23. // $records -- this is the data passed into the template
  24. //
  25. // 'list_files' -- this is the name of the template macro. The actual
  26. // location of the file is all handled by the
  27. // template engine which knows about stuff like
  28. // the current skin
  29. //
  30. CCPage::PageArg('records',$records,'list_files');
  31. }

Display HTML (Custom Template Macro)

Put an XML template with phpTAL metal:block macros into the Skins Path. Bind a URL to a method and invoke those macros from PHP.

<metal:block define-macro="my_macro" >
    <!-- local_files/skin/my_template.xml -->
    <select>
       <option repeat="record records" value="record/upload_id" >
            ${record/upload_name}
       </option>
    </select>
</metal:block>
  1. function use_my_template($extra_tags='')
  2. {
  3. if( !empty($extra_tags) )
  4. $extra_tags = ", $extra_tags";
  5.  
  6. // retrieve the records...
  7. //
  8. require_once('cclib/cc-query.php');
  9. $query = new CCQuery();
  10. list( $records ) = $query->Query(array('f'=>'php','tags'=>"remix$exrra_tags"));
  11.  
  12. // Tell the skin where to find your macro
  13. //
  14. // This says: When I ask for 'some_name' I really
  15. // mean 'my_template.xml/my_macro'
  16. //
  17. CCPage::PageArg('some_name', 'my_template.xml/my_macro' );
  18.  
  19. // This says: invoke the macro mapped to 'some_name'
  20. // passing in the data in $records and call it
  21. // 'records'
  22. //
  23. CCPage::PageArg('records',$records,'list_files');
  24. }

Display HTML (For a Query)

If the all you are listing out are upload records you definitely use the Query API and a custom template. You don't need to bind any urls or write any PHP code at all to set that up. The admin docs have a example of how that works.


AJAX in ccHost

ccHost uses prototype.js "as is" on the client for AJAX requests.

To return JSON use the Zend JSON encoder already included in ccHost

Returning JSON

  1. {
  2. // setup data as native PHP
  3. //
  4. $return_vals = array( 'msg' => 'hello world' );
  5.  
  6. // encode as JSON
  7. require_once('cclib/zend/json-encoder.php');
  8. $json = CCZend_Json_Encoder::encode($return_vals);
  9. // JSON wants the data in the header
  10. header( "X-JSON: $text");
  11. header( 'Content-type: text/plain');
  12. print($text);
  13.  
  14. // exit the session so nothing else gets printed
  15. exit;
  16. }

Returning HTML (Snippet)

To return a snippet of HTML create a template macro in your Skins Path. Then invoke it using the special CCTemplateMacro class.

<metal:block define-macro="print_msg" >
    <!-- local_files/skin/my_template.xml -->
    <div class="message"> ${args/msg} </div>
</metal:block>
  1. {
  2. // data to send into template
  3. //
  4. $args = array( 'msg' => 'hello world' );
  5.  
  6. // Using this class you will have access to
  7. // all the current skins macros
  8. //
  9. $template = new CCTemplateMacro( 'my_template.xml', 'print_msg' );
  10.  
  11. // This call presumes the macro is waiting for
  12. // a set a variables called 'args'
  13. //
  14. $template->SetAllAndPrint('args',$args);
  15.  
  16. // exit the session so nothing gets printed
  17. exit;
  18. }

Returning HTML (Formatted Snippet)

To return a snippet of HTML that has all the formatting and scripts of the current skin, create a template macro in your Skins Path. Then invoke it as did for placing a macro on a page but call {@CCPage::ShowHeaderFooter} with false,false to chop off the banner, menus and footers. This leaves the <head> section of the skin in tact allowing you to enjoy the benefits of the style sheets and javascript references of the current skin. The method is useful for popups.

  1. function use_my_template($extra_tags='')
  2. {
  3. if( !empty($extra_tags) )
  4. $extra_tags = ", $extra_tags";
  5.  
  6. // retrieve the records...
  7. //
  8. require_once('cclib/cc-query.php');
  9. $query = new CCQuery();
  10. list( $records ) = $query->Query(array('f'=>'php','tags'=>"remix$exrra_tags"));
  11.  
  12. // Tell the skin where to find your macro
  13. //
  14. // This says: When I ask for 'some_name' I really
  15. // mean 'my_template.xml/my_macro'
  16. //
  17. CCPage::PageArg('some_name', 'my_template.xml/my_macro' );
  18.  
  19. // This says: invoke the macro mapped to 'some_name'
  20. // passing in the data in $records and call it
  21. // 'records'
  22. //
  23. CCPage::PageArg('records',$records,'list_files');
  24.  
  25. // We will print the page, but without 'adornments'
  26. //
  27. CCPage::ShowHeaderFooter(false,false);
  28. }

Terminology

"row" vs. "record"

The CCTable object has several methods with the word 'row' in it, QueryRows, QueryRow and QueryKeyRow. Whenever you see the word 'row' in the code it (almost) always refers to a database table row as it is stored in the database (a.k.a the 'raw' data). Retrieving a row is generally a very quick operation but the data in the row is of specialized, limited use.

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. // get the upload with the upload_id of '101'
  4. $row = $uploads->QueryKeyRow(101);
  5. $is_published = $row['upload_published'];
  6. $is_banned = $row['upload_banned'];
  7. }

Meanwhile a 'record' is a row that has been heavily massaged. Retrieving the record is much more heavy weight operation but results in a lot more data that should have just about everything you ever wanted to know about that upload, user, topic, etc.

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. // get the upload with the upload_id of '101'
  4. $record = $uploads->GetRecordFromKey(101);
  5. $args['upload_href'] = $record['file_page_url'];
  6. $args['upload_text'] = $record['upload_name'];
  7. $args['user_href'] = $record['artist_page_url'];
  8. $args['user_text'] = $record['user_real_name'];
  9. //...

In order to get fully familiar with the what kind of data is available in both rows and records, it is highly recommended that you set up a debug environment (Develepment Environment) and dump the contents using the GetRecordFromRow method. First the row:

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. // get the upload with the upload_id of '101'
  4. $row = $uploads->QueryKeyRow(101);
  5. CCDebug::PrintVar($row);
  6. //...

...then the record:

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. // get the upload with the upload_id of '101'
  4. $row = $uploads->QueryKeyRow(101);
  5. $record =& $uploads->GetRecordFromRow($row);
  6. CCDebug::PrintVar($record);
  7. //...

If you determine that you really need the records and not the rows then you can skip the conversion step and call GetRecords:

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. $where = "upload_name LIKE '%sunny%'";
  4. $records =& $uploads->GetRecords($where); // Returns an array
  5. // Don't use foreach with records, it's too expensive
  6. $count = count($records);
  7. for( $i = 0; $i < $count; $i++ )
  8. {
  9. $record =& $records[$i];
  10. //...

"upload" vs "file"

An 'upload' is a database row that represents the meta information that the user entered for their submission. It has the name, tags, description, etc. stored in it. Use the CCUploads table object to access this data.

An upload can have more than one physical file stored on your server. Users upload these files using the 'Manage Files' menu option on the upload's main page. Each file has it's own specific meta data such as the file format, the physical name, system tags, etc. The recommended way of accessing the specific data about a file is through the files field of an upload record ("row" vs. "record"):

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. $where = "upload_name LIKE '%sunny%'";
  4. $records =& $uploads->GetRecords($where);
  5. $count = count($records);
  6. for( $i = 0; $i < $count; $i++ )
  7. {
  8. $record =& $records[$i];
  9. $files =& $record['files'];
  10. $fcount = count($files);
  11. for( $n = 0; $n < $fcount; $n++ )
  12. {
  13. $file =& $files[$n];
  14. //...

To get familiar with the contents of the file record you should set up a debug environment (Develepment Environment) to inspect the data:

  1. {
  2. $uploads =& CCUploads::GetTable();
  3. $where = "upload_name LIKE '%sunny%'";
  4. $records =& $uploads->GetRecords($where);
  5. $count = count($records);
  6. for( $i = 0; $i < $count; $i++ )
  7. {
  8. $record =& $records[$i];
  9. CCDebug::PrintVar($record['files']);
  10. //...

"key" vs "id"

The terms 'key' and 'id' are used interchangeably throughout ccHost code. They both refer to the PRIMARY KEY column in any table which are unique numeric references within the table.



Documentation generated on Sat, 17 Nov 2007 01:03:07 +0000 by phpDocumentor 1.3.0RC4