Docs Home Install Upgrade Troubleshoot Customize Admins Devs Content Query Templates Commands Skins

Debugging

Make sure you know about variable dumps.

Mapping URLs to custom code

In order to map a PHP function to the url some/url:

   CCEvents::AddHandler(CC_EVENT_MAP_URLS, 'map_urls_handler');

   function map_urls_handler()
   {
       CCEvents::MapUrl( ccp( 'some', 'url' ), 'some_url_handler', CC_DONT_CARE_LOGGED_IN );
   }

   function some_url_handler_handler()
   {
   }

If the user appends to the URI path (e.g. some/url/arg1/arg2) those will be passed in as arguments, so you should write your handler like this:

   function some_url_handler( $arg1='', $arg2='' )
   {
   }

Note: Every time you change the URL map you must perform ?update=1 on your site

URL Permissions

The last parameter in CCEvents::MapUrl determines who is allowed to process the URL. These are valid values:

  • CC_MUST_BE_LOGGED_IN Registered users
  • CC_ONLY_NOT_LOGGED_IN Anonymous users only
  • CC_DONT_CARE_LOGGED_IN Everybody
  • CC_ADMIN_ONLY Admins only
  • CC_SUPER_ONLY Super admins only

Add or remove form fields

Editing the fields of the form

Sometimes you may want to change the fields in a form that already exists in the system (e.g. the upload content form). In order to add form fields, remove them or change some properties on existing one, you first need to register a handler for the CC_EVENT_FORM_FIELDS event and direct it to a PHP function.

This event is triggered every time a form is built, but before the HTML is generated for it. The 'form' parameter is a reference to an object derived from CCForm and 'fields' parameter is a reference to an array that has all the current information about the fields.

Add fields

Put the following into a module with a .PHP extension into the <local_files>/lib directory:

// 1. Register for form_fields event
CCEvents::AddHandler(CC_EVENT_FORM_FIELDS, 'form_fields_handler');

// 2. Here's the handler
function form_fields_handler( &$form, &$fields )
{
    // 3. Uncomment these lines to peek into the variables...
       // CCDebug::Enable(true);
       // CCDebug::PrintVar($form);

    // 4. Here is where we check to see if it's a form we care about

    if( is_subclass_of($form,'CCUploadMediaForm') ||
                   is_subclass_of($form,'ccuploadmediaform') )
    {
           /*
           * 5. Check if the field already in the field list. 
           *
           * The name of our example field is 'upload_bpm' - you can
           * name yours whatever you like.
           *
           */
           if( empty($fields['upload_bpm']) )
           {
               // 6. The field does not exist, let's add it...
                 
               $fields['upload_bpm'] = 
                           array( 'label'  => 'Tempo',
                                  'form_tip'   => 'Please enter the tempo in beats per minute',
                                   'class'     => 'form_input_short',
                                  'formatter'  => 'textedit',
                                  'flags'      => CCFF_NOUPDATE);
            }
        }
 }


All forms in the system are of a particular class so you can check for the class name or a derivation of that class in order to make sure you have the right. In step 4. above we are only interested in content submit forms.

Always check to see if the field you are adding hasn't already been added and if you are deleting or editing that the field actually exists using the PHP 'empty()' as in step 5. above.

Remove an existing field

If you want to remove a field completely from the forum you use PHP's unset. Replace step 5 above with something like:

 if( isset($fields['topic_name']) )
 {
    unset( $fields['topic_name'] ); // this will remove the topic name field from form
 }

This can have bad side-effects if the system's form POST handling code expects the field to be there. A better option may be to hide the field in the form:

 $form->SetHiddenField( 'topic_name' );

Edit existing fields

If you want to change the properties of field (like say, change the Topic Name field from a 'textedit' to a larger 'textarea' when submitting a form message or review) it would look something like this:

       if( is_subclass_of($form,'CCTopicForm') ||
                   is_subclass_of($form,'cctopicform') )
       {
           /*
           * First we check to make sure the field exists at all
           */
           if( !empty($fields['topic_name']) )
           {
               // override whatever was there with 'textarea'

               $fields['topic_name']['formatter'] = 'textarea';
            }
        }

Pre-filling the field with values

Sometimes a record in the system is edited and we have a custom field in the form we would want to pre-populate the value (like when the user selects 'Edit Properties' for an upload). In order to set the value of field before it is displayed to the user, you need to capture the event that is triggered as the form is populating with values:

   CCEvents::AddHandler(CC_EVENT_FORM_POPULATE,  'form_populate_handler');

   function form_populate_handler(&$form,&$values)
   {
       // check to see if it's the kind of form we 
       // care about...

       if( !is_subclass_of($form,'CCUploadMediaForm') &&
                   !is_subclass_of($form,'ccuploadmediaform') )
       {
           return;
       }

       // $values might have our value in it (in the case
       // of an upload "Edit Properties" form 
       if( !empty($values['upload_extra']['bpm']) )
       {
           $value = $values['upload_extra']['bpm'];

           // Call 'SetFormValue' with our value...

           $form->SetFormValue( 'upload_bpm', $value );
       }
   }

Validating your form field's user input

If you've added a new field you must validate the user's input and notify them if something causes the value to be rejected. This is phase of the form POST handling where the code extracts the value from PHP's $_POST array and transfers the data to ccHost's field list.

Here's the hook and hanlder for that scenario:

   CCEvents::AddHandler(CC_EVENT_FORM_VERIFY,  'form_verify_handler');

   function form_verify_handler( &$form, &$retval )
   {
       if( !is_subclass_of($form,'CCUploadMediaForm') &&
                   !is_subclass_of($form,'ccuploadmediaform') )
       {
           // this is not a form we care about 
           return;
       }
       
       if( !array_key_exists('upload_bpm', $_POST) )
       {
           // for whatever reason, our field is not in the form _POST
           return;
       }

       // It's always a good idea to 'strip' user' input from a form

       $bpm = CCUtil::StripText($_POST['upload_bpm']);
       if( empty($bpm) )
           return;


       $ok = 'YOUR_CODE_TO_VALIDATE_GOES_HERE';

       if( $ok )
       {
           // success! Set it into the form

           $form->SetFormValue('upload_bpm',$bpm );
       }
       else
       {
           // wups, stop the form post, return the user and show this error:

           $form->SetFieldError( 'upload_bpm',  
                                 "BPM must be between 30 and 200" );
        }

        $retval = $ok; // Make to tell the form system what happened (!!!)

        return $ok;
   }

Saving your form field's user input

You probably want to commit the data in a form to disk for use in later sessions. When to do that depends on the operation. For example if the form you are manipulating is the upload submit form, then you should register for the even triggered when everything else in the upload has succeeded and dig your value out of $_POST.

Here's an example we do just that:

 CCEvents::AddHandler( CC_EVENT_UPLOAD_DONE, 'upload_done_handler' );

 function upload_done_handler( $upload_id, $op )
 {
       if( ($op == CC_UF_NEW_UPLOAD || $op == CC_UF_PROPERTIES_EDIT) &&
           array_key_exists('upload_bpm',$_POST)
         )
       {
           $uploads =& CCUploads::GetTable();
           $uploads->SetExtraField( $upload_id, 'bpm', $_POST['upload_bpm']);
       }
   }

Add data per user

Note: this only works in 5.0.1 or later

In order to add data to a user's record you will need their unique identifier number - this is the index key in cc_tbl_user. If you don't have it readily you should have the short user name - this is the user_name field in cc_tbl_user - and convert it to the id number.

  $user_id = CCUser::IDFromName($user_name);

If you are looking for the currently logged in user you can call:

  $user_id = CCUser::CurrentUser();  // Returns -1 if failed (not logged in)

To save your data to the user's record:

  $users =& CCUsers::GetTable(); // note the 's' in CCUsers
  $users->SetExtraField($user_id,'YOUR_DATA_KEY_GOES_HERE', 'YOUR_VALUE_GOES_HERE' );


To retrieve it at a later time:

  $users =& CCUsers::GetTable();
  $value = $users->SetExtraField($user_id,'YOUR_DATA_KEY_GOES_HERE');

If you are using SQL you will next expand the 'user_extra' field like this:

  $user_row = CCDatabase::QueryRow('SELECT * FROM cc_tbl_user WHERE user_id = ' . $user_id );
  $user_extra = unserialize($user_row['user_extra']);
  $value = $user_extra['YOUR_DATA_KEY_GOES_HERE'];

If you want to remove the data key and it's value completely from the record (e.g. to save space), then use the following method:

  $users =& CCUsers::GetTable();
  $users->UnsetExtraField($user_id,'YOUR_DATA_KEY_GOES_HERE');


Add data per upload

In order to add data to an upload record you will need their unique identifier number - this is the index key in cc_tbl_uploads.

To save your data to the upload record:

  $uploads =& CCUploads::GetTable();
  $uploads->SetExtraField($upload_id,'YOUR_DATA_KEY_GOES_HERE', 'YOUR_VALUE_GOES_HERE' );


To retrieve it at a later time:

  $uploads =& CCUploads::GetTable();
  $value = $uploads->SetExtraField($upload_id,'YOUR_DATA_KEY_GOES_HERE');

If you are using SQL you will next expand the 'upload_extra' field like this:

  $upload_row = CCDatabase::QueryRow('SELECT * FROM cc_tbl_uploads WHERE upload_id = ' . $upload_id );
  $upload_extra = unserialize($upload_row['upload_extra']);
  $value = $upload_extra['YOUR_DATA_KEY_GOES_HERE'];

If you want to remove the data key and it's value completely from the record (e.g. to save space), then use the following method:

  $uploads =& CCUploads::GetTable();
  $uploads->UnsetExtraField($upload_id,'YOUR_DATA_KEY_GOES_HERE');

Using custom data in a template

The typical template will display a record that is in a loop. The custom data is probably going to be in upload_extra or user_extra depending on the type of record you are displaying.

The PHP template version:

 foreach( $A['records'] as $R )
 {
      if( !empty($R['upload_extra']['YOUR_DATA_KEY_HERE']) )
      {
        print '<span>' . $R['upload_extra']['YOUR_DATA_KEY_HERE'] . '</span>';
      }
 }

The TPL version:

 %loop(records,R)%
     %if_not_null(#R/upload_extra/YOUR_DATA_KEY_HERE)%
         <span>%(#R/upload_extra/YOUR_DATA_KEY_HERE)%</span>
     %end_if%
 %end_loop%

Note that the upload_extra field is not guaranteed to be available in the template. You have to ask for it specifically in the dataview that is feeding the template.

Saving and retrieving system configuration data

Saving data to the ccHost configuration system is done two ways:

Writing Directly to Configs

Using the global 'config' option set

If you just want to save a single value in the global scope then you should use the 'config' group. You must put that into an array:

    $configs =& CCConfigs::GetTable();
    $args['my_setting'] = 'SOME_VALUE_OTHER_THAN_AN_OBJECT';
    $configs->SaveConfig( 'config', $args, '', true );

Make sure the last parameter (merge) is set to true so you don't overwrite the global config.

You can later access your setting via the global variable called $CC_GLOBALS. They are accessed like this:

 global $CC_GLOBALS;

 $option1 = $CC_GLOBALS['my_setting'];


Using the local 'settings' option set

If you just want to save a single value to the current virtual root then you should use the 'settings' group.

    $configs =& CCConfigs::GetTable();
    $args['my_local_setting'] = 'SOME_VALUE_OTHER_THAN_AN_OBJECT';
    $configs->SaveConfig( 'settings', $args, '', true );

Make sure the last parameter is set to true so you don't overwrite the system's settings.

Later you can access your setting via a GetConfig call:

   $configs =& CCConfigs::GetTable();
   $settings = $configs->GetConfig('settings');
   $value = $settings['my_local_setting'];

The advantage of using use 'settings' instead of 'config' is that you allow admins to have different values for each virtual root. This is always preferable if the resources you use for your feature can accommodate.

Creating your own options set

Often you will need to save more than just one value in which case you will want to create your own, complete option set.

Naming

The name of your option set must be a unique name that refers to only this set of options. The following names are used by the system:

browse_query_tags channels chart config extras format-allow groups id3-tag-masks licenses 
logging menu name-masks playlist_tags pseudo-verify query-browser-opts query-browser-reqtags 
remote_files settings site-logo skin-settings submit_forms tab_pages tcache throttle 
throttle_rules tmacs ttag urlmap 

If you overwrite any of these by using their names you will surely make your installation unusable.

Allowed values

The value parameter of the Save method is always an array of options which can be any PHP type except object.

  $configs =& CCConfigs::GetTable();

  $whiz_bang_options = array(
                 'enabled' => true,
                 'path'    => '/usr/var/whizbang',
                 'weather' => 'cloudy',
                 'show_on' => 'tuesday');
 
  $save->SaveConfig( 'whiz_bang', $whiz_bang_options );

Often the value is an array of arrays. For example, the menu structure is saved in a way similar to this:

  $configs =& CCConfigs::GetTable();

  $menu_items = array(
      array(
             'label' => 'Submit files',
             'action' => '/submit',
             'access' => CC_LOGGED_IN_ONLY,
             'order'  => 1,
           ),
      array(
             'label'  => 'Find Music',
             'action' => '/view/media/find',
             'access' => CC_DONT_CARE_LOGGED_IN,
             'order'  => 20,
           ),
      <... etc ...>
      );

   $save->SaveConfig( 'menu', $menu_items );

Scoping your option set

If you only want one global copy of your settings to exist throughout the system then you set the third parameter of Save to CC_GLOBAL_SCOPE.

  $save->SaveConfig( 'whiz_bang', $whiz_bang_options, CC_GLOBAL_SCOPE );

If the name is 'config' then the scope argument is ignored because 'config' can only live at the global level.

If the name is anything other than 'config' then the scope defaults to current virtual root which allows your settings to be overwritten by other virtual roots. If this what you want then you must leave the parameter blank.

The vast majority of ccHost installations do not use virtual roots and in those cases the global and local scopes are exactly the same 100% of the time. However, the flexibility of virtual roots has been used to great effect enough times to prove the features worth and you should never assume that local and global are always the same.

Merging or overwriting

By default the Save method will merge the data you are passing in with whatever data is already there. This allows for easy 'tweaking' of sing values in an option set:

  $configs =& CCConfigs::GetTable();
  $tweak['show_on'] = 'wednesday';
  $save->SaveConfig( 'whiz_bang', $whiz_bang_options );

However, sometimes you want to overwrite all the existing data there with the new data. In that case set the fourth parameter of Save ('merge') to false (default is true which means do a merge):

   $save->SaveConfig( 'menu', $menu_items, '', false );

Reading your option set back

Use GetCOnfig to read your option set:

  $configs =& CCConfigs::GetTable();
  $my_option_set = $configs->GetConfig('YOUR_OPTION_SET_NAME');

Hooking the Admins Screens

This section explains how to hook the admin menus and settings screens so you can give admins a chance to edit the values of your config settings.

HINT: Besides the walkthrough below, there are over 50 examples of these types of hook all over the code. Start in cchost_lib/ccextras because that is most likely to be closer to what you are doing than anywhere else.

Hooking 'Global Setup' Form

If you used the global 'config' option set to store your setting(s) then you want to hook the 'Global Setup' form (admin browses to admin/setup.

To do that, put the following code into <local_files>/lib/my_admin_config_hook.php (the name of the file doesn't matter, just the location and that it has a PHP extension):

 CCEvents::AddHandler(CC_EVENT_GET_CONFIG_FIELDS,  'config_fields_handler');
 
 function config_fields_handler(
   {
       if( $scope == CC_GLOBAL_SCOPE )
       {
           $fields['YOUR_SETTINGS_NAME_HERE'] =
              array(  'label'      => 'My wizbang enabled'
                      'value'      => 'Check here to enable my wizbang feature.',
                      'formatter'  => 'checkbox', // any field here will work
                      'flags'      => CCFF_POPULATE);
       }
   }

Hooking 'Settings' Form

If you used the local 'settings' option set to store your setting(s) then you want to hook the 'Settings' form (admin browses to admin/settings.

The code to do that is exactly the same as the code for the global 'config' option above, however you replace the line:

       if( $scope == CC_GLOBAL_SCOPE )

with:

       if( $scope == CC_LOCAL_SCOPE )


To do that, put the following code into <local_files>/lib/my_admin_config_hook.php (the name of the file doesn't matter, just the location and that it has a PHP extension):

Hooking 'Global Settings' Menu

If you used your own option set at the global level then you'll want to create a menu item in the 'Global Settings' menu (admin browses to admin/site/global).

Like all other custom code this should all go into your <local_files>/lib directory and have a PHP extension.

The first then we'll do is make some some defines to avoid typos and show the relationship between key phrases throughout the process:

   define ( 'URL_TO_WIZBANG_ADMIN', 'admin/wizbang' );
   define ( 'NAME_OF_WIZBANG_CONFIG', 'wizbang' );

Next we'll add a menu link to the admin menu. We do that by hooking the event that is called to build that menu:

   // Register to get notified when the admin menu is being built

   CCEvents::AddHandler(CC_EVENT_ADMIN_MENU,  'admin_menu_handler');

   // This function will be called when the admin menu is about to be
   // generated. Add your menu item(s) to '$items'

   function admin_menu_handler( &$items, $scope )
   {
       if( $scope == CC_GLOBAL_SCOPE )
       {
           $items += array(
               NAME_OF_WIZBANG_CONFIG => array( 
                                'menu_text'  => 'My Wizbang Settings',
                                'menu_group' => 'configure',
                                'help' => 'Config settings for My Wizbang functionality',
                                'access' => CC_ADMIN_ONLY,
                                'weight' => 300,
                                'action' =>  ccl( URL_TO_WIZBANG_ADMIN )
                                ),
               );
       }
   }

If you browse to admin/site/global right now you should see your menu item. However clicking on the link won't work because we haven't mapped any function to that URL. Here's how that happens:

   CCEvents::AddHandler(CC_EVENT_MAP_URLS, 'map_urls_handler');

   function map_urls_handler()
   {
       CCEvents::MapUrl( ccp( URL_TO_WIZBANG_ADMIN ), 'admin_mywizbang_handler', CC_ADMIN_ONLY );
   }

At this point you need to perform a <your_root_installation>?update=1 to rebuild the URL map with your new mapping. However the URL still doesn't work because we still need to define the PHP function we mapped to the URL:

   function admin_mywizbang_handler()
   {
      require_once('cchost_lib/cc-page.php');
      require_once('cchost_lib/cc-admin.php');

      // Build up page adornments

      $title = 'Admin Settings for MyWizBang';
      CCPage::SetTitle( $title );
      CCAdmin::BreadCrumbs( true, array('url'=>,'text'=>$title));

      // Display the form

      $form = new CCMyWizBangAdminForm();
      CCPage::AddForm( $form->GenerateForm() );
  }


All the code above does is display a title, bread crumbs and a form. The form however, doesn't exist so we'll define the form here:

   require_once('cchost_lib/cc-form.php');

   class CCMyWizBangAdminForm : extends CCEditConfigForm
   {
      function CCMyWizBangAdminForm()
      {
        $this->CCEditConfigForm( NAME_OF_WIZBANG_CONFIG, CC_GLOBAL_SCOPE ); 

        // replace these fields with something meaningful:

        $fields = array( 
           'option1' =>  // name of the setting
               array(  
                  'label'     => 'Set Option 1 here',
                  'form_tip'  => 'Check this to enable option 1',
                  'value'     => ,
                  'formatter' => 'textedit',
                  'flags'     => CCFF_POPULATE | CCFF_REQUIRED ),

           'option2' =>  
               array(  
                  'label'     => 'Set Option 2 here',
                  'form_tip'  => 'Check this to enable option 2',
                  'value'     => ,
                  'formatter' => 'checkbox',
                  'flags'     => CCFF_POPULATE ),

          );

         $this->SetModule(__FILE__);
         $this->AddFormFields($fields);
       }
   }

There are several things to note about this form that make it work properly:

  • You should keep in mind that including chost_lib/cc-forms.php is a rather heavy weight operation. You should definitely split the code up for basic efficiency.
  • The form you create must extend CCEditConfigForm. There's plenty of magic there so just go with it.
  • The name of your form MUST start with 'CC' and end with 'Form' (e.g. CCxyxForm is good, xycCCForm is not good).
  • You do NOT have to process the $_POST side because ccHost will automatically update your configuration option set. If you feel you must handle the post call:
          $this->SetHandler( 'NAME_OF_POST_HANDLING_FUNCTION' );

in your form's constructor.

Hooking 'Virtual Root Settings' Menu

If you used your own option set at the virtual root (local) level then you'll want to create a menu item in the 'Virtual Root Settings' menu (admin browses to admin/site/local).

The procedure for doing so is exactly the same that for the Global admin menu above with the following differences:

Replace:

   function admin_menu_handler( &$items, $scope )
   {
       if( $scope == CC_GLOBAL_SCOPE )
       {

with:

   function admin_menu_handler( &$items, $scope )
   {
       if( $scope == CC_LOCAL_SCOPE )
       {

and replace:

      function CCMyWizBangAdminForm()
      {
        $this->CCEditConfigForm( NAME_OF_WIZBANG_CONFIG, CC_GLOBAL_SCOPE ); 

with:

      function CCMyWizBangAdminForm()
      {
        $this->CCEditConfigForm( NAME_OF_WIZBANG_CONFIG );