Index: lang/en/admin.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lang/en/admin.php,v
retrieving revision 1.131
diff -u -r1.131 admin.php
--- lang/en/admin.php	9 Oct 2010 18:13:34 -0000	1.131
+++ lang/en/admin.php	13 Oct 2010 12:51:12 -0000
@@ -243,6 +243,7 @@
 $string['configlangmenu'] = 'Choose whether or not you want to display the general-purpose language menu on the home page, login page etc.  This does not affect the user\'s ability to set the preferred language in their own profile.';
 $string['configlatinexcelexport'] = 'Choose the encoding for Excel exports.';
 $string['configlocale'] = 'Choose a sitewide locale - this will override the format and language of dates for all language packs (though names of days in calendar are not affected). You need to have this locale data installed on your operating system (eg for linux en_US.UTF-8 or es_ES.UTF-8). In most cases this field should be left blank.';
+$string['configlogdownloads'] = 'Controls whether file downloads are included in Moodle logs. Potentially includes all data files even if they are displayed directly in the browser.';
 $string['configloginhttps'] = 'Turning this on will make Moodle use a secure https connection just for the login page (providing a secure login), and then afterwards revert back to the normal http URL for general speed.  CAUTION: this setting REQUIRES https to be specifically enabled on the web server - if it is not then YOU COULD LOCK YOURSELF OUT OF YOUR SITE.';
 $string['configloglifetime'] = 'This specifies the length of time you want to keep logs about user activity.  Logs that are older than this age are automatically deleted.  It is best to keep logs as long as possible, in case you need them, but if you have a very busy server and are experiencing performance problems, then you may want to lower the log lifetime. Values lower than 30 are not recommended because statistics may not work properly.';
 $string['configlookahead'] = 'Days to look ahead';
@@ -635,6 +636,9 @@
 $string['locationsettings'] = 'Location settings';
 $string['locked'] = 'locked';
 $string['log'] = 'Logs';
+$string['logdownloads'] = 'Log file downloads';
+$string['logdownloads_exceptsupport'] = 'Yes, except support files (images etc.)';
+$string['logdownloads_allfiles'] = 'Yes, all files';
 $string['loginhttps'] = 'Use HTTPS for logins';
 $string['loglifetime'] = 'Keep logs for';
 $string['longtimewarning'] = '<b>Please note that this process can take a long time.</b>';
Index: course/lib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/lib.php,v
retrieving revision 1.778
diff -u -r1.778 lib.php
--- course/lib.php	21 Sep 2010 08:13:12 -0000	1.778
+++ course/lib.php	13 Oct 2010 12:51:12 -0000
@@ -389,8 +389,10 @@
 
         // If $log->url has been trimmed short by the db size restriction
         // code in add_to_log, keep a note so we don't add a link to a broken url
+        // - also don't add links to empty url
         $tl=textlib_get_instance();
-        $brokenurl=($tl->strlen($log->url)==100 && $tl->substr($log->url,97)=='...');
+        $brokenurl = ($tl->strlen($log->url)==100 && 
+                $tl->substr($log->url,97)=='...') || $log->url === '';
 
         $row = array();
         if ($course->id == SITEID) {
@@ -415,6 +417,13 @@
             $link = make_log_url($log->module,$log->url);
             $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
         }
+
+        // Hack to make the info look better for file downloads
+        if ($log->module == 'filelib' && $log->action == 'download' &&
+                preg_match('~^([0-9]+) ([0-9a-f]+) (.*)$~', $log->info, $matches)) {
+            $log->info = display_size($matches[1]) . ' <small>' . $matches[2] .
+                    '</small><br />' . s($matches[3]);
+        }
         $row[] = $log->info;
         $table->data[] = $row;
     }
Index: admin/settings/security.php
===================================================================
RCS file: /cvsroot/moodle/moodle/admin/settings/security.php,v
retrieving revision 1.53
diff -u -r1.53 security.php
--- admin/settings/security.php	9 Oct 2010 18:13:33 -0000	1.53
+++ admin/settings/security.php	13 Oct 2010 12:51:11 -0000
@@ -72,6 +72,13 @@
     $temp->add(new admin_setting_configcheckbox('groupenrolmentkeypolicy', get_string('groupenrolmentkeypolicy', 'admin'), get_string('groupenrolmentkeypolicy_desc', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('disableuserimages', get_string('disableuserimages', 'admin'), get_string('configdisableuserimages', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('emailchangeconfirmation', get_string('emailchangeconfirmation', 'admin'), get_string('configemailchangeconfirmation', 'admin'), 1));
+
+    $exceptsupportfiles = '-js,css,jpg,png,gif,svg';
+    $temp->add(new admin_setting_configselect('logdownloads', get_string('logdownloads', 'admin'), get_string('configlogdownloads', 'admin'), '', array(
+            $exceptsupportfiles => get_string('logdownloads_exceptsupport', 'admin'),
+            '-' => get_string('logdownloads_allfiles', 'admin'),
+            '' => get_string('no'))));
+
     $ADMIN->add('security', $temp);
 
 
Index: lib/simpletest/testfilelib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/simpletest/testfilelib.php,v
retrieving revision 1.18
diff -u -r1.18 testfilelib.php
--- lib/simpletest/testfilelib.php	5 Jul 2010 02:56:05 -0000	1.18
+++ lib/simpletest/testfilelib.php	13 Oct 2010 12:51:12 -0000
@@ -79,4 +79,33 @@
         $postdata = format_postdata_for_curlcall($postdatatoconvert);
         $this->assertEqual($postdata, $expectedresult);
     }
+
+    public function test_should_log_download() {
+        // Note there is no need to reset the $CFG value as it is not possible
+        // that a file download occurs in the same request as a unit test run
+        global $CFG;
+        
+        // Example filenames
+        $gif = 'example.gif';
+        $doc = 'example.doc';
+
+        $CFG->logdownloads = '';
+        $this->assertFalse(should_log_download($gif));
+        $this->assertFalse(should_log_download($doc));
+
+        // Exclude list of no types should mean everything is logged
+        $CFG->logdownloads = '-';
+        $this->assertTrue(should_log_download($gif));
+        $this->assertTrue(should_log_download($doc));
+
+        // Exclude list of gif, png, etc should mean gif is not logged
+        $CFG->logdownloads = '-gif,png,css';
+        $this->assertFalse(should_log_download($gif));
+        $this->assertTrue(should_log_download($doc));
+
+        // Include list of doc, html, etc should mean doc is logged
+        $CFG->logdownloads = 'html,doc';
+        $this->assertFalse(should_log_download($gif));
+        $this->assertTrue(should_log_download($doc));
+    }
 }
Index: lib/filelib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/filelib.php,v
retrieving revision 1.214
diff -u -r1.214 filelib.php
--- lib/filelib.php	12 Oct 2010 14:56:01 -0000	1.214
+++ lib/filelib.php	13 Oct 2010 12:51:12 -0000
@@ -1763,13 +1763,44 @@
 }
 
 /**
+ * Function returns true if downloads are set to be logged for the given file.
+ * @param string $filename Name of file (only used to check extension)
+ * @return bool True if download should be logged
+ */
+function should_log_download($filename) {
+    global $CFG;
+
+    // If it is turned off, do not log
+    if (empty($CFG->logdownloads)) {
+        return false;
+    }
+
+    // logdownloads may EITHER be an exclude OR an include list.
+    // Exclude list starts with minus. To log everything, use '-' on its own
+    if (substr($CFG->logdownloads, 0, 1) === '-') {
+        $includelist = false;
+        // List is split on comma and/or space and/or dot (in case people
+        // include dot in the extensions)
+        $types = preg_split('~[, .]+~', substr($CFG->logdownloads, 1));
+    } else {
+        $includelist = true;
+        $types = preg_split('~[, .]+~', $CFG->logdownloads);
+    }
+    $ext = preg_replace('~^.*\.~', '', $filename);
+    $inlist = in_array($ext, $types);
+
+    // If it's included and in the list, or excluded and not in it
+    return ($includelist == $inlist);
+}
+
+/**
  * Handles the sending of file data to the user's browser, including support for
  * byteranges etc.
  *
  * @global object
  * @global object
  * @global object
- * @param object $stored_file local file object
+ * @param stored_file $stored_file local file object
  * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
  * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
  * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
@@ -1800,6 +1831,19 @@
     $lastmodified = $stored_file->get_timemodified();
     $filesize     = $stored_file->get_filesize();
 
+    // Log file if logging is enabled
+    if (should_log_download($filename)) {
+        // The URL is only 100 characters which means we cannot reliably
+        // store the filename in it, so let's just leave it blank. (Otherwise
+        // it causes debug warnings which are annoying.)
+        $logurl = '';
+
+        // Add entry to log. Note filename is last in info because it may
+        // include a space (and will get chopped off if too long).
+        add_to_log($COURSE->id, 'filelib', 'download', $logurl, $filesize .
+                ' ' . $stored_file->get_contenthash() . ' ' . $filename);
+    }
+
     //IE compatibiltiy HACK!
     if (ini_get('zlib.output_compression')) {
         ini_set('zlib.output_compression', 'Off');
