Cchost/developer/tutorial/Hooking File Events

From Creative Commons
Jump to: navigation, search


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


In this tutorial we create a placeholder for code that will expand archive files upon upload. How you actually expand the archive will be left to you (third party library, shell out to a command line utility, etc.)

We want to achieve the following items in this customization:

  • Hook the file upload event and expand any archives so the files reside in the user's content area.
  • Hook the file deleted event to make sure the files are clean up (deleted) along with the main archive file.
  • Replace the system's 'download' template (which normally displays which files are available for download) to include the expanded archive files.

Note that this code assumes that file has been tagged archive by the file verifier. This is done automatically for ZIP files but you would have to expand the Pseudo Verifier (found on the 'Global Settings' menu) to include other archives such as gzip, bz, 7z, etc. Just make sure that if you add those file types that you specify the archive tag to be added to the file record in order for this code to work properly.

You can place all the PHP code in this tutorial into one file called <local_files>/lib/auto_expand_archive.php You can call the file whatever you like, just make sure it has a PHP extension and resides in your /lib directory.

Let's start by hooking the file upload event and expanding the archive:

   CCEvents::AddHandler(CC_EVENT_FILE_DONE,   'unzip_file_on_upload' );

   function unzip_file_on_upload( &$file_args )
   {
       if( $file_args['file_format_info']['media-type'] != 'archive' )
           return;

       $archive_location = $file_args['local_path'];
       $target_dir = dirname($archive_location) . '/archives/' . $file_args['file_id'];
       CCUtil::MakeSubDirs($target_dir);

       /*********************************************************************
       *
       *   Add code here to un-archive $archive_location to $target_dir
       *
       ***********************************************************************/
   }

The function above is called every time the user adds a file to an upload record (including for new upload records). If the file is marked with an archive tag, then the a new directory is created under the user's content upload area called archives/<file_id>. Using the file_id guarantees a unique directory for each archive. Putting all the archives into one directory makes it easier for sysadmins to spot these cases.

Now let's hook the file delete event:

   CCEvents::AddHandler( CC_EVENT_DELETE_FILE, 'delete_archive_files_on_delete' );

   function delete_archive_files_on_delete( $file_id )
   {
       $upload_id = CCDatabase::QueryItem('SELECT file_upload FROM ' . 
                                          'cc_tbl_files WHERE file_id ='. 
                                          $file_id);

       $user_name = CCDatabase::QueryItem(
                          'SELECT user_name FROM cc_tbl_user ' .
                          'JOIN cc_tbl_uploads ON user_id=upload_user WHERE upload_id='.
                          $upload_id);
 
       global $CC_GLOBALS;

       $archive_dir = cca($CC_GLOBALS['user-upload-root'],$user_name,'archives',$file_id);

       if( !file_exists($archive_dir) )
           return;

       $files = array();
       dig_out_archive_files($archive_dir,$files);

       // warning: this code deletes the files but does
       // NOT remove the directories (you should do that)
       foreach( $files as $file )
           unlink($file);
   }

In the function above we check to see the for the existence of an archive directory for this file_id. If one exists we get a list of all the files and delete each one. (Note this code will leave behind the directory sturcture. This probably not what you want to do but we'll leave it as an exercise for you to fill that in.

In order to dig out the files in the archive directory we use a recursive helper function:

   function dig_out_archive_files($dir,&$files)
   {
       $dirs = glob( $dir . '*', GLOB_ONLYDIR|GLOB_NOSORT|GLOB_MARK );
       foreach( $dirs as $subdir )
           dig_out_archive_files($subdir,$files);
       $found = glob( $dir . '*.*' );
       foreach( $found as $find )
           if( !is_dir($find) )
               $files[] = $find; 
   }

Next we are going to include a function that will be used by the 'download' template to display all the files in the archive directory, display them as downloadable links.

   function print_archive_files($R,$F)
   {
       global $CC_GLOBALS;

       $base_dir = dirname( $F['local_path'] );
       $archive_dir = $base_dir . '/archives/' . $F['file_id'];
       if( file_exists($archive_dir) )
       {
           $files = array();
           dig_out_archive_files($archive_dir,$files);
           foreach( $files as $file )
           {
               $file = str_replace($base_dir,'',$file);
               $file = str_replace('\\','/',$file);
               if( $file{0} == '/' )
                   $file = substr($file,1);
               $link_text = str_replace( 'archives/' . $F['file_id'] . '/', '', $file);
               $adownload_url = ccd($CC_GLOBALS['user-upload-root'],$R['user_name'],$file);

               print '<br /> -- <a href="' . $adownload_url . '">' . $link_text . '</a>';
           }
       }
   }

In the function above we assume that template will pass in two parameters: An upload record and a file record. It will check to see if the file has an archives directory. If it does, it will call our recursive helper function to get a list of all the files in the archives directory and display them as downloadable links. You might want to add some other useful information like the file size of the items.

Finally, we want to replace the 'download' template itself so show these files. In order to do this we will take advantage of ccHost's template search routine which will look in your skins directory before the system's. Make sure to put this file in <local_files>/skins/download.tpl with exactly that name.

   %%
   [meta]
       type = ajax_component
       desc = _('Download files')
       dataview = files
   [/meta]
   %%
   <div  id="cc_download">
   <div id="download_help">
       <div>%text(str_list_IEtip)%</div>
       <div>%text(str_list_Mactip)%</div>
   </div>
   %loop(records,R)%
   <p class="upload_name">%(#R/upload_name)%</p>
   <ol>
       %loop(#R/files,F)%
            <li>
               %(#F/file_nicname)% %(#F/file_filesize)%: 
                    <a href="%(#F/download_url)%">%(#F/file_name)%</a> 
               <? print_archive_files($R,$F); ?>
            </li>
       %end_loop%
   </ol>
   %end_loop%

You can see in the template above we've injected our call to print_archive_file after printing a link to the archive file itself.

Now when users click on the 'download' link on an upload page or as part of an upload listing, this template will show.