Merge branch 'MDL-33699' of git://github.com/netspotau/moodle-mod_assign
authorDan Poltawski <dan@moodle.com>
Mon, 18 Jun 2012 05:49:16 +0000 (13:49 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 18 Jun 2012 05:49:16 +0000 (13:49 +0800)
48 files changed:
backup/moodle2/backup_final_task.class.php
backup/moodle2/backup_root_task.class.php
backup/util/plan/backup_plan.class.php
course/externallib.php
course/format/renderer.php
course/renderer.php
enrol/yui/notification/notification.js
filter/activitynames/db/install.php [new file with mode: 0644]
filter/glossary/filter.php
filter/glossary/yui/autolinker/autolinker.js
filter/mediaplugin/db/install.php
filter/upgrade.txt
lib/boxlib.php
lib/csslib.php
lib/filelib.php
lib/filterlib.php
lib/form/filemanager.js
lib/moodlelib.php
lib/tests/csslib_test.php [moved from lib/tests/cssslib_test.php with 52% similarity]
lib/weblib.php
mod/assign/db/messages.php
mod/assign/gradingactionsform.php [deleted file]
mod/assign/gradingbatchoperationsform.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/quickgradingform.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/upgradelib.php
mod/assign/version.php
question/engine/tests/helpers.php
question/type/questiontypebase.php
repository/boxnet/lib.php
repository/dropbox/lib.php
repository/filesystem/lib.php
repository/flickr/lib.php
repository/flickr_public/lib.php
repository/lib.php
repository/repository_ajax.php
repository/s3/lib.php
repository/upload/lib.php
repository/url/lib.php
repository/wikimedia/lib.php
theme/mymobile/config.php
theme/mymobile/javascript/jquery-noconflict.js [new file with mode: 0644]
theme/mymobile/layout/embedded.php
version.php

index abbecb8..45d491a 100644 (file)
@@ -113,27 +113,27 @@ class backup_final_task extends backup_task {
 
         require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php');
 
-        //Checking if we have some converter involved in the process
-        $converters = convert_helper::available_converters(false);
-        //Conversion status
+        // Look for converter steps only in type course and mode general backup operations.
         $conversion = false;
-        foreach ($converters as $value) {
-            if ($this->get_setting_value($value)) {
-                //zip class
-                $zip_contents      = "{$value}_zip_contents";
-                $store_backup_file = "{$value}_store_backup_file";
-                $convert           = "{$value}_backup_convert";
-
-                $this->add_step(new $convert("package_convert_{$value}"));
-                $this->add_step(new $zip_contents("zip_contents_{$value}"));
-                $this->add_step(new $store_backup_file("save_backupfile_{$value}"));
-                if (!$conversion) {
-                    $conversion = true;
+        if ($this->plan->get_type() == backup::TYPE_1COURSE and $this->plan->get_mode() == backup::MODE_GENERAL) {
+            $converters = convert_helper::available_converters(false);
+            foreach ($converters as $value) {
+                if ($this->get_setting_value($value)) {
+                    // Zip class.
+                    $zip_contents      = "{$value}_zip_contents";
+                    $store_backup_file = "{$value}_store_backup_file";
+                    $convert           = "{$value}_backup_convert";
+
+                    $this->add_step(new $convert("package_convert_{$value}"));
+                    $this->add_step(new $zip_contents("zip_contents_{$value}"));
+                    $this->add_step(new $store_backup_file("save_backupfile_{$value}"));
+                    if (!$conversion) {
+                        $conversion = true;
+                    }
                 }
             }
         }
 
-
         // On backup::MODE_IMPORT, we don't have to zip nor store the the file, skip these steps
         if (($this->plan->get_mode() != backup::MODE_IMPORT) && !$conversion) {
             // Generate the zip file (mbz extension)
index 3bd46cf..cf25616 100644 (file)
@@ -70,12 +70,15 @@ class backup_root_task extends backup_task {
         $filename->set_ui_filename(get_string('filename', 'backup'), 'backup.mbz', array('size'=>50));
         $this->add_setting($filename);
 
-        //Sample custom settings
-        $converters = convert_helper::available_converters(false);
-        foreach ($converters as $cnv) {
-            $formatcnv = new backup_users_setting($cnv, base_setting::IS_BOOLEAN, false);
-            $formatcnv->set_ui(new backup_setting_ui_checkbox($formatcnv, get_string('backupformat'.$cnv, 'backup')));
-            $this->add_setting($formatcnv);
+        // Present converter settings only in type course and mode general backup operations.
+        $converters = array();
+        if ($this->plan->get_type() == backup::TYPE_1COURSE and $this->plan->get_mode() == backup::MODE_GENERAL) {
+            $converters = convert_helper::available_converters(false);
+            foreach ($converters as $cnv) {
+                $formatcnv = new backup_users_setting($cnv, base_setting::IS_BOOLEAN, false);
+                $formatcnv->set_ui(new backup_setting_ui_checkbox($formatcnv, get_string('backupformat'.$cnv, 'backup')));
+                $this->add_setting($formatcnv);
+            }
         }
 
         // Define users setting (keeping it on hand to define dependencies)
index bf217fd..4378221 100644 (file)
@@ -67,6 +67,10 @@ class backup_plan extends base_plan implements loggable {
         return $this->controller->get_backupid();
     }
 
+    public function get_type() {
+        return $this->controller->get_type();
+    }
+
     public function get_mode() {
         return $this->controller->get_mode();
     }
index e58d5cd..e14ce90 100644 (file)
@@ -910,7 +910,7 @@ class core_course_external extends external_api {
                                          ' - user must have \'moodle/category:manage\' to search on theme'),
                             'value' => new external_value(PARAM_RAW, 'the value to match')
                         )
-                    ), VALUE_DEFAULT, array()
+                    ), 'criteria', VALUE_DEFAULT, array()
                 ),
                 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
                                           (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
index b662e1e..f0655a9 100644 (file)
@@ -324,6 +324,11 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         foreach ($modsequence as $cmid) {
             $thismod = $mods[$cmid];
 
+            if ($thismod->modname == 'label') {
+                // Labels are special (not interesting for students)!
+                continue;
+            }
+
             if ($thismod->uservisible) {
                 if (isset($sectionmods[$thismod->modname])) {
                     $sectionmods[$thismod->modname]['count']++;
index d43d15c..2206a5e 100644 (file)
@@ -208,10 +208,10 @@ class core_course_renderer extends plugin_renderer_base {
         $formcontent .= html_writer::end_tag('div'); // types
 
         $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
-        $formcontent .= html_writer::tag('input', '',
-                array('type' => 'submit', 'name' => 'addcancel', 'id' => 'addcancel', 'value' => get_string('cancel')));
         $formcontent .= html_writer::tag('input', '',
                 array('type' => 'submit', 'name' => 'submitbutton', 'id' => 'submitbutton', 'value' => get_string('add')));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'submit', 'name' => 'addcancel', 'id' => 'addcancel', 'value' => get_string('cancel')));
         $formcontent .= html_writer::end_tag('div');
         $formcontent .= html_writer::end_tag('form');
 
index 352dd6e..8b91443 100644 (file)
@@ -76,6 +76,7 @@ Y.extend(DIALOGUE, Y.Overlay, {
                 if (this.get('draggable')) {
                     var titlebar = '#' + this.get('id') + ' .' + CSS.HEADER;
                     this.plug(Y.Plugin.Drag, {handles : [titlebar]});
+                    this.dd.addInvalid('div.closebutton');
                     Y.one(titlebar).setStyle('cursor', 'move');
                 }
                 break;
diff --git a/filter/activitynames/db/install.php b/filter/activitynames/db/install.php
new file mode 100644 (file)
index 0000000..8caef86
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Filter post install hook
+ *
+ * @package    filter_activitynames
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+function xmldb_filter_activitynames_install() {
+    global $CFG;
+    require_once("$CFG->libdir/filterlib.php");
+
+    filter_set_global_state('filter/activitynames', TEXTFILTER_ON);
+}
+
index 20879d9..259c668 100644 (file)
@@ -35,14 +35,25 @@ defined('MOODLE_INTERNAL') || die();
  */
 class filter_glossary extends moodle_text_filter {
 
+    public function setup($page, $context) {
+        // This only requires execution once per request.
+        static $jsinitialised = false;
+        if (empty($jsinitialised)) {
+            $page->requires->yui_module(
+                    'moodle-filter_glossary-autolinker',
+                    'M.filter_glossary.init_filter_autolinking',
+                    array(array('courseid' => 0)));
+            $jsinitialised = true;
+        }
+    }
+
     public function filter($text, array $options = array()) {
-        global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS, $PAGE;
+        global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS;
 
         // Trivial-cache - keyed on $cachedcontextid
         static $cachedcontextid;
         static $conceptlist;
 
-        static $jsinitialised;       // To control unique js init
         static $nothingtodo;         // To avoid processing if no glossaries / concepts are found
 
         // Try to get current course
@@ -185,16 +196,6 @@ class filter_glossary extends moodle_text_filter {
             }
 
             $conceptlist = filter_remove_duplicates($conceptlist);
-
-            if (empty($jsinitialised)) {
-                // Add a JavaScript event to open popup's here. This only ever need to be
-                // added once!
-                $PAGE->requires->yui_module(
-                        'moodle-filter_glossary-autolinker',
-                        'M.filter_glossary.init_filter_autolinking',
-                        array(array('courseid' => $courseid)));
-                $jsinitialised = true;
-            }
         }
 
         if (!empty($GLOSSARY_EXCLUDECONCEPTS)) {
index e9af017..18677af 100644 (file)
@@ -5,7 +5,6 @@ YUI.add('moodle-filter_glossary-autolinker', function(Y) {
         POPUPNAME = 'name',
         POPUPOPTIONS = 'options',
         TITLE = 'title',
-        COURSEID = 'courseid',
         WIDTH = 'width',
         HEIGHT = 'height',
         MENUBAR = 'menubar',
@@ -122,8 +121,7 @@ YUI.add('moodle-filter_glossary-autolinker', function(Y) {
             status : {value : true},
             directories : {value : false},
             fullscreen : {value : false},
-            dependent : {value : true},
-            courseid : {value : 1}
+            dependent : {value : true}
         }
     });
 
index 1200eb6..64e3fb9 100644 (file)
@@ -25,6 +25,8 @@
 
 function xmldb_filter_mediaplugin_install() {
     global $CFG;
+    require_once("$CFG->libdir/filterlib.php");
 
+    filter_set_global_state('filter/mediaplugin', TEXTFILTER_ON);
 }
 
index bb0522e..cf94ace 100644 (file)
@@ -1,6 +1,13 @@
 This file describes API changes in core filter API and plugins,
 information provided here is intended especially for developers.
 
+=== 2.3 ===
+
+* new setup() method added to moodle_text_filter, invoked before
+  filtering happens, used to add all the requirements to the page
+  (js, css...) and/or other init tasks. See filter/glossary for
+  an example using the API (and MDL-32279 for its justification).
+
 === 2.2 ===
 
 * legacy filters and legacy locations have been deprecated, so any
index a647223..e1e8387 100644 (file)
@@ -188,6 +188,29 @@ class boxclient {
         return $ret;
     }
 
+    /**
+     * Get box.net file info
+     *
+     * @param string $fileid
+     * @return string|null
+     */
+    function get_file_info($fileid) {
+        $this->_clearErrors();
+        $params = array();
+        $params['action']     = 'get_file_info';
+        $params['file_id']    = $fileid;
+        $params['auth_token'] = $this->auth_token;
+        $params['api_key']    = $this->api_key;
+        $http = new curl(array('debug'=>$this->debug, 'cache'=>true, 'module_cache'=>'repository'));
+        $xml = $http->get($this->_box_api_url, $params);
+        $o = simplexml_load_string(trim($xml));
+        if ($o->status == 's_get_file_info') {
+            return $o->info;
+        } else {
+            return null;
+        }
+    }
+
     /**
      * @param array $sax
      * @param array $tree Passed by reference
index 3ae362f..3032433 100644 (file)
 /**
  * This file contains CSS related class, and function for the CSS optimiser
  *
- * Please see the {@see css_optimiser} class for greater detail.
+ * Please see the {@link css_optimiser} class for greater detail.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-//NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
+// NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
 
 /**
  * Stores CSS in a file at the given path.
@@ -176,7 +176,7 @@ function css_send_cached_css($csspath, $etag) {
  * that theme designers could utilise the optimised output during development to
  * help them optimise their CSS... not that they should write lazy CSS.
  *
- * @param string CSS
+ * @param string $css
  */
 function css_send_uncached_css($css, $themesupportsoptimisation = true) {
     global $CFG;
@@ -300,29 +300,36 @@ EOD;
  *    - HSL colour:  hsl(0-360, 0-100%, 0-100%)
  *    - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
  *
- * Or a recognised browser colour mapping {@see css_optimiser::$htmlcolours}
+ * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
  *
  * @param string $value The colour value to check
  * @return bool
  */
 function css_is_colour($value) {
     $value = trim($value);
+
+    $hex  = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
+    $rgb  = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
+    $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
+    $hsl  = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
+    $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
+
     if (in_array(strtolower($value), array('inherit'))) {
         return true;
-    } else if (preg_match('/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/', $value)) {
+    } else if (preg_match($hex, $value)) {
         return true;
     } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
         return true;
-    } else if (preg_match('#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
+    } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
         // It is an RGB colour
         return true;
-    } else if (preg_match('#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
+    } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
         // It is an RGBA colour
         return true;
-    } else if (preg_match('#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
+    } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
         // It is an HSL colour
         return true;
-    } else if (preg_match('#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
+    } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
         // It is an HSLA colour
         return true;
     }
@@ -343,7 +350,7 @@ function css_is_width($value) {
     if (in_array(strtolower($value), array('auto', 'inherit'))) {
         return true;
     }
-    if (preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|%|in|cm|mm|ex|pc)?$#i', $value)) {
+    if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
         return true;
     }
     return false;
@@ -375,7 +382,7 @@ function css_sort_by_count(array $a, array $b) {
  * ensure we collect all mappings, at the end of the processing those styles are
  * then combined into an optimised form to keep them as short as possible.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -492,10 +499,21 @@ class css_optimiser {
     public function process($css) {
         global $CFG;
 
+        // Easiest win there is
+        $css = trim($css);
+
         $this->reset_stats();
         $this->timestart = microtime(true);
         $this->rawstrlen = strlen($css);
 
+        // Don't try to process files with no content... it just doesn't make sense.
+        // But we should produce an error for them, an empty CSS file will lead to a
+        // useless request for those running theme designer mode.
+        if ($this->rawstrlen === 0) {
+            $this->errors[] = 'Skipping file as it has no content.';
+            return '';
+        }
+
         // First up we need to remove all line breaks - this allows us to instantly
         // reduce our processing requirements and as we will process everything
         // into a new structure there's really nothing lost.
@@ -510,6 +528,8 @@ class css_optimiser {
         );
         $imports = array();
         $charset = false;
+        // Keyframes are used for CSS animation they will be processed right at the very end.
+        $keyframes = array();
 
         $currentprocess = self::PROCESSING_START;
         $currentrule = css_rule::init();
@@ -534,7 +554,7 @@ class css_optimiser {
                 $suspectatrule = true;
             }
             switch ($currentprocess) {
-                // Start processing an at rule e.g. @media, @page
+                // Start processing an @ rule e.g. @media, @page, @keyframes
                 case self::PROCESSING_ATRULE:
                     switch ($char) {
                         case ';':
@@ -548,14 +568,17 @@ class css_optimiser {
                                     $currentprocess = self::PROCESSING_SELECTORS;
                                 }
                             }
-                            $buffer = '';
-                            $currentatrule = false;
+                            if ($currentatrule !== 'media') {
+                                $buffer = '';
+                                $currentatrule = false;
+                            }
                             // continue 1: The switch processing chars
                             // continue 2: The switch processing the state
                             // continue 3: The for loop
                             continue 3;
                         case '{':
-                            if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)#', $buffer, $matches)) {
+                            if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#', $buffer, $matches)) {
+                                // Basic media declaration
                                 $mediatypes = str_replace(' ', '', $matches[1]);
                                 if (!array_key_exists($mediatypes, $medias)) {
                                     $medias[$mediatypes] = new css_media($mediatypes);
@@ -563,6 +586,24 @@ class css_optimiser {
                                 $currentmedia = $medias[$mediatypes];
                                 $currentprocess = self::PROCESSING_SELECTORS;
                                 $buffer = '';
+                            } else if ($currentatrule == 'media' && preg_match('#\s*@media\s*([^{]+)#', $buffer, $matches)) {
+                                // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/
+                                $mediatypes = $matches[1];
+                                $hash = md5($mediatypes);
+                                $medias[$hash] = new css_media($mediatypes);
+                                $currentmedia = $medias[$hash];
+                                $currentprocess = self::PROCESSING_SELECTORS;
+                                $buffer = '';
+                            } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
+                                // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
+                                // them to be overridden to ensure we don't mess anything up. (means we keep everything in order)
+                                $keyframefor = $matches[1];
+                                $keyframename = $matches[3];
+                                $keyframe = new css_keyframe($keyframefor, $keyframename);
+                                $keyframes[] = $keyframe;
+                                $currentmedia = $keyframe;
+                                $currentprocess = self::PROCESSING_SELECTORS;
+                                $buffer = '';
                             }
                             // continue 1: The switch processing chars
                             // continue 2: The switch processing the state
@@ -596,8 +637,9 @@ class css_optimiser {
                                 continue 3;
                             }
                             if (!empty($buffer)) {
-                                if ($suspectatrule && preg_match('#@(media|import|charset)\s*#', $buffer, $matches)) {
-                                    $currentatrule = $matches[1];
+                                // Check for known @ rules
+                                if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
+                                    $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
                                     $currentprocess = self::PROCESSING_ATRULE;
                                     $buffer .= $char;
                                 } else {
@@ -617,8 +659,9 @@ class css_optimiser {
                                 // continue 3: The for loop
                                 continue 3;
                             }
-
-                            $currentselector->add($buffer);
+                            if ($buffer !== '') {
+                                $currentselector->add($buffer);
+                            }
                             $currentrule->add_selector($currentselector);
                             $currentselector = css_selector::init();
                             $currentprocess = self::PROCESSING_STYLES;
@@ -639,6 +682,10 @@ class css_optimiser {
                                 $currentmedia = $medias['all'];
                                 $currentatrule = false;
                                 $buffer = '';
+                            } else if (strpos($currentatrule, 'keyframes') !== false) {
+                                $currentmedia = $medias['all'];
+                                $currentatrule = false;
+                                $buffer = '';
                             }
                             // continue 1: The switch processing chars
                             // continue 2: The switch processing the state
@@ -727,6 +774,24 @@ class css_optimiser {
             $buffer .= $char;
         }
 
+        foreach ($medias as $media) {
+            $this->optimise($media);
+        }
+        $css = $this->produce_css($charset, $imports, $medias, $keyframes);
+
+        $this->timecomplete = microtime(true);
+        return trim($css);
+    }
+
+    /**
+     * Produces CSS for the given charset, imports, media, and keyframes
+     * @param string $charset
+     * @param array $imports
+     * @param array $medias
+     * @param array $keyframes
+     * @return string
+     */
+    protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
         $css = '';
         if (!empty($charset)) {
             $imports[] = $charset;
@@ -735,19 +800,64 @@ class css_optimiser {
             $css .= implode("\n", $imports);
             $css .= "\n\n";
         }
+
+        $cssreset = array();
+        $cssstandard = array();
+        $csskeyframes = array();
+
+        // Process each media declaration individually
         foreach ($medias as $media) {
-            $media->organise_rules_by_selectors();
-            $this->optimisedrules += $media->count_rules();
-            $this->optimisedselectors +=  $media->count_selectors();
-            if ($media->has_errors()) {
-                $this->errors[] = $media->get_errors();
+            // If this declaration applies to all media types
+            if (in_array('all', $media->get_types())) {
+                // Collect all rules that represet reset rules and remove them from the media object at the same time.
+                // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
+                // can't end up out of order because of optimisation.
+                $resetrules = $media->get_reset_rules(true);
+                if (!empty($resetrules)) {
+                    $cssreset[] = css_writer::media('all', $resetrules);
+                }
             }
-            $css .= $media->out();
+            // Get the standard cSS
+            $cssstandard[] = $media->out();
         }
+
+        // Finally if there are any keyframe declarations process them now.
+        if (count($keyframes) > 0) {
+            foreach ($keyframes as $keyframe) {
+                $this->optimisedrules += $keyframe->count_rules();
+                $this->optimisedselectors +=  $keyframe->count_selectors();
+                if ($keyframe->has_errors()) {
+                    $this->errors += $keyframe->get_errors();
+                }
+                $csskeyframes[] = $keyframe->out();
+            }
+        }
+
+        // Join it all together
+        $css .= join('', $cssreset);
+        $css .= join('', $cssstandard);
+        $css .= join('', $csskeyframes);
+
+        // Record the strlenght of the now optimised CSS.
         $this->optimisedstrlen = strlen($css);
 
-        $this->timecomplete = microtime(true);
-        return trim($css);
+        // Return the now produced CSS
+        return $css;
+    }
+
+    /**
+     * Optimises the CSS rules within a rule collection of one form or another
+     *
+     * @param css_rule_collection $media
+     * @return void This function acts in reference
+     */
+    protected function optimise(css_rule_collection $media) {
+        $media->organise_rules_by_selectors();
+        $this->optimisedrules += $media->count_rules();
+        $this->optimisedselectors +=  $media->count_selectors();
+        if ($media->has_errors()) {
+            $this->errors += $media->get_errors();
+        }
     }
 
     /**
@@ -765,11 +875,21 @@ class css_optimiser {
             'rawrules'              => $this->rawrules,
             'optimisedstrlen'       => $this->optimisedstrlen,
             'optimisedrules'        => $this->optimisedrules,
-            'optimisedselectors'     => $this->optimisedselectors,
-            'improvementstrlen'     => round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%',
-            'improvementrules'      => round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%',
-            'improvementselectors'  => round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%',
+            'optimisedselectors'    => $this->optimisedselectors,
+            'improvementstrlen'     => '-',
+            'improvementrules'     => '-',
+            'improvementselectors'     => '-',
         );
+        // Avoid division by 0 errors by checking we have valid raw values
+        if ($this->rawstrlen > 0) {
+            $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
+        }
+        if ($this->rawrules > 0) {
+            $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
+        }
+        if ($this->rawselectors > 0) {
+            $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
+        }
         return $stats;
     }
 
@@ -785,10 +905,16 @@ class css_optimiser {
     /**
      * Returns an array of errors that have occured
      *
+     * @param bool $clear If set to true the errors will be cleared after being returned.
      * @return array
      */
-    public function get_errors() {
-        return $this->errors;
+    public function get_errors($clear = false) {
+        $errors = $this->errors;
+        if ($clear) {
+            // Reset the error array
+            $this->errors = array();
+        }
+        return $errors;
     }
 
     /**
@@ -812,15 +938,22 @@ class css_optimiser {
      * @return string
      */
     public function output_stats_css() {
-        $stats = $this->get_stats();
-
-        $strlenimprovement = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1);
-        $ruleimprovement = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1);
-        $selectorimprovement = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1);
-        $timetaken = round($this->timecomplete - $this->timestart, 4);
 
         $computedcss  = "/****************************************\n";
         $computedcss .= " *------- CSS Optimisation stats --------\n";
+
+        if ($this->rawstrlen === 0) {
+            $computedcss .= " File not processed as it has no content /\n\n";
+            $computedcss .= " ****************************************/\n\n";
+            return $computedcss;
+        } else if ($this->rawrules === 0) {
+            $computedcss .= " File contained no rules to be processed /\n\n";
+            $computedcss .= " ****************************************/\n\n";
+            return $computedcss;
+        }
+
+        $stats = $this->get_stats();
+
         $computedcss .= " *  ".date('r')."\n";
         $computedcss .= " *  {$stats['commentsincss']}  \t comments removed\n";
         $computedcss .= " *  Optimisation took {$stats['timetaken']} seconds\n";
@@ -1020,7 +1153,7 @@ class css_optimiser {
 /**
  * Used to prepare CSS strings
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -1087,7 +1220,7 @@ abstract class css_writer {
 
         $output = '';
         if ($typestring !== 'all') {
-            $output .= $nl.$nl."@media {$typestring} {".$nl;
+            $output .= "\n@media {$typestring} {".$nl;
             self::increase_indent();
         }
         foreach ($rules as $rule) {
@@ -1100,6 +1233,25 @@ abstract class css_writer {
         return $output;
     }
 
+    /**
+     * Returns CSS for a keyframe
+     *
+     * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
+     * @param string $name The name for the keyframe
+     * @param array $rules An array of rules belonging to the keyframe
+     * @return string
+     */
+    public static function keyframe($for, $name, array &$rules) {
+        $nl = self::get_separator();
+
+        $output = "\n@{$for} {$name} {";
+        foreach ($rules as $rule) {
+            $output .= $rule->out();
+        }
+        $output .= '}';
+        return $output;
+    }
+
     /**
      * Returns CSS for a rule
      *
@@ -1146,6 +1298,15 @@ abstract class css_writer {
     public static function styles(array $styles) {
         $bits = array();
         foreach ($styles as $style) {
+            // Check if the style is an array. If it is then we are outputing an advanced style.
+            // An advanced style is a style with one or more values, and can occur in situations like background-image
+            // where browse specific values are being used.
+            if (is_array($style)) {
+                foreach ($style as $advstyle) {
+                    $bits[] = $advstyle->out();
+                }
+                continue;
+            }
             $bits[] = $style->out();
         }
         return join('', $bits);
@@ -1160,6 +1321,7 @@ abstract class css_writer {
      * @return string
      */
     public static function style($name, $value, $important = false) {
+        $value = trim($value);
         if ($important && strpos($value, '!important') === false) {
             $value .= ' !important';
         }
@@ -1173,7 +1335,7 @@ abstract class css_writer {
  * The selector is the classes, id, elements, and psuedo bits that make up a CSS
  * rule.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -1192,6 +1354,13 @@ class css_selector {
      */
     protected $count = 0;
 
+    /**
+     * Is null if there are no selectors, true if all selectors are basic and false otherwise.
+     * A basic selector is one that consists of just the element type. e.g. div, span, td, a
+     * @var bool|null
+     */
+    protected $isbasic = null;
+
     /**
      * Initialises a new CSS selector
      * @return css_selector
@@ -1216,6 +1385,16 @@ class css_selector {
         if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
             $count ++;
         }
+        // If its already false then no need to continue, its not basic
+        if ($this->isbasic !== false) {
+            // If theres more than one part making up this selector its not basic
+            if ($count > 1) {
+                $this->isbasic = false;
+            } else {
+                // Check whether it is a basic element (a-z+) with possible psuedo selector
+                $this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
+            }
+        }
         $this->count = $count;
         $this->selectors[] = $selector;
     }
@@ -1234,12 +1413,20 @@ class css_selector {
     public function out() {
         return css_writer::selector($this->selectors);
     }
+
+    /**
+     * Returns true is all of the selectors act only upon basic elements (no classes/ids)
+     * @return bool
+     */
+    public function is_basic() {
+        return ($this->isbasic === true);
+    }
 }
 
 /**
  * A structure to represent a CSS rule.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -1247,13 +1434,13 @@ class css_selector {
 class css_rule {
 
     /**
-     * An array of CSS selectors {@see css_selector}
+     * An array of CSS selectors {@link css_selector}
      * @var array
      */
     protected $selectors = array();
 
     /**
-     * An array of CSS styles {@see css_style}
+     * An array of CSS styles {@link css_style}
      * @var array
      */
     protected $styles = array();
@@ -1320,7 +1507,18 @@ class css_rule {
         }
         if ($style instanceof css_style) {
             $name = $style->get_name();
-            if (array_key_exists($name, $this->styles)) {
+            $exists = array_key_exists($name, $this->styles);
+            // We need to find out if the current style support multiple values, or whether the style
+            // is already set up to record multiple values. This can happen with background images which can have single
+            // and multiple values.
+            if ($style->allows_multiple_values() || ($exists && is_array($this->styles[$name]))) {
+                if (!$exists) {
+                    $this->styles[$name] = array();
+                } else if ($this->styles[$name] instanceof css_style) {
+                    $this->styles[$name] = array($this->styles[$name]);
+                }
+                $this->styles[$name][] = $style;
+            } else if ($exists) {
                 $this->styles[$name]->set_value($style->get_value());
             } else {
                 $this->styles[$name] = $style;
@@ -1337,7 +1535,7 @@ class css_rule {
     /**
      * An easy method of adding several styles at once. Just calls add_style.
      *
-     * This method simply iterates over the array and calls {@see css_rule::add_style()}
+     * This method simply iterates over the array and calls {@link css_rule::add_style()}
      * with each.
      *
      * @param array $styles Adds an array of styles
@@ -1383,27 +1581,70 @@ class css_rule {
      * @return array An array of consolidated styles
      */
     public function get_consolidated_styles() {
+        $organisedstyles = array();
         $finalstyles = array();
         $consolidate = array();
+        $advancedstyles = array();
         foreach ($this->styles as $style) {
+            // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
+            // one or more values. Background-image is one such example as it can have browser specific styles.
+            if (is_array($style)) {
+                $single = null;
+                $count = 0;
+                foreach ($style as $advstyle) {
+                    $key = $count++;
+                    $advancedstyles[$key] = $advstyle;
+                    if (!$advstyle->allows_multiple_values()) {
+                        if (!is_null($single)) {
+                            unset($advancedstyles[$single]);
+                        }
+                        $single = $key;
+                    }
+                }
+                if (!is_null($single)) {
+                    $style = $advancedstyles[$single];
+
+                    $consolidatetoclass = $style->consolidate_to();
+                    if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
+                        $class = 'css_style_'.$consolidatetoclass;
+                        if (!array_key_exists($class, $consolidate)) {
+                            $consolidate[$class] = array();
+                            $organisedstyles[$class] = true;
+                        }
+                        $consolidate[$class][] = $style;
+                        unset($advancedstyles[$single]);
+                    }
+                }
+
+                continue;
+            }
             $consolidatetoclass = $style->consolidate_to();
-            if ($style->is_valid() && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
+            if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
                 $class = 'css_style_'.$consolidatetoclass;
                 if (!array_key_exists($class, $consolidate)) {
                     $consolidate[$class] = array();
+                    $organisedstyles[$class] = true;
                 }
                 $consolidate[$class][] = $style;
             } else {
-                $finalstyles[] = $style;
+                $organisedstyles[$style->get_name()] = $style;
             }
         }
 
         foreach ($consolidate as $class => $styles) {
-            $styles = $class::consolidate($styles);
-            foreach ($styles as $style) {
+            $organisedstyles[$class] = $class::consolidate($styles);
+        }
+
+        foreach ($organisedstyles as $style) {
+            if (is_array($style)) {
+                foreach ($style as $s) {
+                    $finalstyles[] = $s;
+                }
+            } else {
                 $finalstyles[] = $style;
             }
         }
+        $finalstyles = array_merge($finalstyles, $advancedstyles);
         return $finalstyles;
     }
 
@@ -1430,6 +1671,10 @@ class css_rule {
     public function split_by_style() {
         $return = array();
         foreach ($this->styles as $style) {
+            if (is_array($style)) {
+                $return[] = new css_rule($this->selectors, $style);
+                continue;
+            }
             $return[] = new css_rule($this->selectors, array($style));
         }
         return $return;
@@ -1473,6 +1718,14 @@ class css_rule {
      */
     public function has_errors() {
         foreach ($this->styles as $style) {
+            if (is_array($style)) {
+                foreach ($style as $advstyle) {
+                    if ($advstyle->has_error()) {
+                        return true;
+                    }
+                }
+                continue;
+            }
             if ($style->has_error()) {
                 return true;
             }
@@ -1483,7 +1736,7 @@ class css_rule {
     /**
      * Returns the error strings that were recorded when processing this rule.
      *
-     * Before calling this function you should first call {@see css_rule::has_errors()}
+     * Before calling this function you should first call {@link css_rule::has_errors()}
      * to make sure there are errors (hopefully there arn't).
      *
      * @return string
@@ -1497,44 +1750,51 @@ class css_rule {
             }
         }
         return $css." has the following errors:\n".join("\n", $errors);
+    }
 
+    /**
+     * Returns true if this rule could be considered a reset rule.
+     *
+     * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
+     *
+     * @return bool
+     */
+    public function is_reset_rule() {
+        foreach ($this->selectors as $selector) {
+            if (!$selector->is_basic()) {
+                return false;
+            }
+        }
+        return true;
     }
 }
 
 /**
- * A media class to organise rules by the media they apply to.
+ * An abstract CSS rule collection class.
  *
- * @package core_css
+ * This class is extended by things such as media and keyframe declaration. They are declarations that
+ * group rules together for a purpose.
+ * When no declaration is specified rules accumulate into @media all.
+ *
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class css_media {
-
-    /**
-     * An array of the different media types this instance applies to.
-     * @var array
-     */
-    protected $types = array();
-
+abstract class css_rule_collection {
     /**
-     * An array of rules within this media instance
+     * An array of rules within this collection instance
      * @var array
      */
     protected $rules = array();
 
     /**
-     * Initalises a new media instance
-     *
-     * @param string $for The media that the contained rules are destined for.
+     * The collection must be able to print itself.
      */
-    public function __construct($for = 'all') {
-        $types = explode(',', $for);
-        $this->types = array_map('trim', $types);
-    }
+    abstract public function out();
 
     /**
-     * Adds a new CSS rule to this media instance
+     * Adds a new CSS rule to this collection instance
      *
      * @param css_rule $newrule
      */
@@ -1550,7 +1810,7 @@ class css_media {
     }
 
     /**
-     * Returns the rules used by this
+     * Returns the rules used by this collection
      *
      * @return array
      */
@@ -1567,23 +1827,30 @@ class css_media {
     public function organise_rules_by_selectors() {
         $optimised = array();
         $beforecount = count($this->rules);
+        $lasthash = null;
+        $lastrule = null;
         foreach ($this->rules as $rule) {
             $hash = $rule->get_style_hash();
-            if (!array_key_exists($hash, $optimised)) {
-                $optimised[$hash] = clone($rule);
-            } else {
+            if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) {
                 foreach ($rule->get_selectors() as $selector) {
-                    $optimised[$hash]->add_selector($selector);
+                    $lastrule->add_selector($selector);
                 }
+                continue;
             }
+            $lastrule = clone($rule);
+            $lasthash = $hash;
+            $optimised[] = $lastrule;
+        }
+        $this->rules = array();
+        foreach ($optimised as $optimised) {
+            $this->rules[$optimised->get_selector_hash()] = $optimised;
         }
-        $this->rules = $optimised;
         $aftercount = count($this->rules);
         return ($beforecount < $aftercount);
     }
 
     /**
-     * Returns the total number of rules that exist within this media set
+     * Returns the total number of rules that exist within this collection
      *
      * @return int
      */
@@ -1592,7 +1859,7 @@ class css_media {
     }
 
     /**
-     * Returns the total number of selectors that exist within this media set
+     * Returns the total number of selectors that exist within this collection
      *
      * @return int
      */
@@ -1604,6 +1871,62 @@ class css_media {
         return $count;
     }
 
+    /**
+     * Returns true if the collection has any rules that have errors
+     *
+     * @return boolean
+     */
+    public function has_errors() {
+        foreach ($this->rules as $rule) {
+            if ($rule->has_errors()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns any errors that have happened within rules in this collection.
+     *
+     * @return string
+     */
+    public function get_errors() {
+        $errors = array();
+        foreach ($this->rules as $rule) {
+            if ($rule->has_errors()) {
+                $errors[] = $rule->get_error_string();
+            }
+        }
+        return $errors;
+    }
+}
+
+/**
+ * A media class to organise rules by the media they apply to.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_media extends css_rule_collection {
+
+    /**
+     * An array of the different media types this instance applies to.
+     * @var array
+     */
+    protected $types = array();
+
+    /**
+     * Initalises a new media instance
+     *
+     * @param string $for The media that the contained rules are destined for.
+     */
+    public function __construct($for = 'all') {
+        $types = explode(',', $for);
+        $this->types = array_map('trim', $types);
+    }
+
     /**
      * Returns the CSS for this media and all of its rules.
      *
@@ -1623,45 +1946,88 @@ class css_media {
     }
 
     /**
-     * Returns true if the media has any rules that have errors
-     *
-     * @return boolean
+     * Returns all of the reset rules known by this media set.
+     * @param bool $remove If set to true reset rules will be removed before being returned.
+     * @return array
      */
-    public function has_errors() {
-        foreach ($this->rules as $rule) {
-            if ($rule->has_errors()) {
-                return true;
+    public function get_reset_rules($remove = false) {
+        $resetrules = array();
+        foreach ($this->rules as $key => $rule) {
+            if ($rule->is_reset_rule()) {
+                $resetrules[] = clone $rule;
+                if ($remove) {
+                    unset($this->rules[$key]);
+                }
             }
         }
-        return false;
+        return $resetrules;
     }
+}
 
+/**
+ * A media class to organise rules by the media they apply to.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_keyframe extends css_rule_collection {
+
+    /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes  */
+    protected $for;
+
+    /** @var string $name The name for the keyframes */
+    protected $name;
+    /**
+     * Constructs a new keyframe
+     *
+     * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
+     * @param string $name The name for the keyframes
+     */
+    public function __construct($for, $name) {
+        $this->for = $for;
+        $this->name = $name;
+    }
     /**
-     * Returns any errors that have happened within rules in this media set.
+     * Returns the directive of this keyframe
      *
+     * e.g. keyframes, -moz-keyframes, -webkit-keyframes
      * @return string
      */
-    public function get_errors() {
-        $errors = array();
-        foreach ($this->rules as $rule) {
-            if ($rule->has_errors()) {
-                $errors[] = $rule->get_error_string();
-            }
-        }
-        return join("\n", $errors);
+    public function get_for() {
+        return $this->for;
+    }
+    /**
+     * Returns the name of this keyframe
+     * @return string
+     */
+    public function get_name() {
+        return $this->name;
+    }
+    /**
+     * Returns the CSS for this collection of keyframes and all of its rules.
+     *
+     * @return string
+     */
+    public function out() {
+        return css_writer::keyframe($this->for, $this->name, $this->rules);
     }
 }
 
 /**
  * An absract class to represent CSS styles
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class css_style {
 
+    /** Constant used for recongise a special empty value in a CSS style */
+    const NULL_VALUE = '@@$NULL$@@';
+
     /**
      * The name of the style
      * @var string
@@ -1696,7 +2062,6 @@ abstract class css_style {
      */
     protected $errormessage = null;
 
-
     /**
      * Initialises a new style.
      *
@@ -1736,6 +2101,7 @@ abstract class css_style {
         $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
         if ($important) {
             $value = substr($value, 0, -(strlen($matches[1])));
+            $value = rtrim($value);
         }
         if (!$this->important || $important) {
             $this->value = $this->clean_value($value);
@@ -1767,11 +2133,12 @@ abstract class css_style {
     /**
      * Returns the value for the style
      *
+     * @param bool $includeimportant If set to true and the rule is important !important postfix will be used.
      * @return string
      */
-    public function get_value() {
+    public function get_value($includeimportant = true) {
         $value = $this->value;
-        if ($this->important) {
+        if ($includeimportant && $this->important) {
             $value .= ' !important';
         }
         return $value;
@@ -1838,12 +2205,58 @@ abstract class css_style {
     public function get_last_error() {
         return $this->errormessage;
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This should only be overriden in circumstances where a shorthand style can lead
+     * to move explicit styles being overwritten. Not a common place occurenace.
+     *
+     * Example:
+     *   This occurs if the shorthand background property was used but no proper value
+     *   was specified for this style.
+     *   This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return false;
+    }
+
+    /**
+     * Returns true if this style permits multiple values.
+     *
+     * This occurs for styles such as background image that can have browser specific values that need to be maintained because
+     * of course we don't know what browser the user is using, and optimisation occurs before caching.
+     * Thus we must always server all values we encounter in the order we encounter them for when this is set to true.
+     *
+     * @return boolean False by default, true if the style supports muliple values.
+     */
+    public function allows_multiple_values() {
+        return false;
+    }
+
+    /**
+     * Returns true if this style was marked important.
+     * @return bool
+     */
+    public function is_important() {
+        return !empty($this->important);
+    }
+
+    /**
+     * Sets the important flag for this style and its current value.
+     * @param bool $important
+     */
+    public function set_important($important = true) {
+        $this->important = (bool) $important;
+    }
 }
 
 /**
  * A generic CSS style class to use when a more specific class does not exist.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -1869,7 +2282,7 @@ class css_style_generic extends css_style {
 /**
  * A colour CSS style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -1954,7 +2367,7 @@ class css_style_color extends css_style {
 /**
  * A width style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2000,7 +2413,7 @@ class css_style_width extends css_style {
 /**
  * A margin style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2058,25 +2471,80 @@ class css_style_margin extends css_style_width {
         if (count($styles) != 4) {
             return $styles;
         }
-        $top = $right = $bottom = $left = null;
+
+        $someimportant = false;
+        $allimportant = null;
+        $notimportantequal = null;
+        $firstvalue = null;
         foreach ($styles as $style) {
-            switch ($style->get_name()) {
-                case 'margin-top' : $top = $style->get_value();break;
-                case 'margin-right' : $right = $style->get_value();break;
-                case 'margin-bottom' : $bottom = $style->get_value();break;
-                case 'margin-left' : $left = $style->get_value();break;
+            if ($style->is_important()) {
+                $someimportant = true;
+                if ($allimportant === null) {
+                    $allimportant = true;
+                }
+            } else {
+                if ($allimportant === true) {
+                    $allimportant = false;
+                }
+                if ($firstvalue == null) {
+                    $firstvalue = $style->get_value(false);
+                    $notimportantequal = true;
+                } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
+                    $notimportantequal = false;
+                }
             }
         }
-        if ($top == $bottom && $left == $right) {
-            if ($top == $left) {
-                return array(new css_style_margin('margin', $top));
-            } else {
-                return array(new css_style_margin('margin', "{$top} {$left}"));
+
+        if ($someimportant && !$allimportant && !$notimportantequal) {
+            return $styles;
+        }
+
+        if ($someimportant && !$allimportant && $notimportantequal) {
+            $return = array(
+                new css_style_margin('margin', $firstvalue)
+            );
+            foreach ($styles as $style) {
+                if ($style->is_important()) {
+                    $return[] = $style;
+                }
             }
-        } else if ($left == $right) {
-            return array(new css_style_margin('margin', "{$top} {$right} {$bottom}"));
+            return $return;
         } else {
-            return array(new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}"));
+            $top = null;
+            $right = null;
+            $bottom = null;
+            $left = null;
+            foreach ($styles as $style) {
+                switch ($style->get_name()) {
+                    case 'margin-top' :
+                        $top = $style->get_value(false);
+                        break;
+                    case 'margin-right' :
+                        $right = $style->get_value(false);
+                        break;
+                    case 'margin-bottom' :
+                        $bottom = $style->get_value(false);
+                        break;
+                    case 'margin-left' :
+                        $left = $style->get_value(false);
+                        break;
+                }
+            }
+            if ($top == $bottom && $left == $right) {
+                if ($top == $left) {
+                    $returnstyle = new css_style_margin('margin', $top);
+                } else {
+                    $returnstyle = new css_style_margin('margin', "{$top} {$left}");
+                }
+            } else if ($left == $right) {
+                $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
+            } else {
+                $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
+            }
+            if ($allimportant) {
+                $returnstyle->set_important();
+            }
+            return array($returnstyle);
         }
     }
 }
@@ -2084,7 +2552,7 @@ class css_style_margin extends css_style_width {
 /**
  * A margin top style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2114,7 +2582,7 @@ class css_style_margintop extends css_style_margin {
 /**
  * A margin right style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2144,7 +2612,7 @@ class css_style_marginright extends css_style_margin {
 /**
  * A margin bottom style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2174,7 +2642,7 @@ class css_style_marginbottom extends css_style_margin {
 /**
  * A margin left style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2204,7 +2672,7 @@ class css_style_marginleft extends css_style_margin {
 /**
  * A border style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2224,7 +2692,7 @@ class css_style_border extends css_style {
         $return = array();
         if (count($bits) > 0) {
             $width = array_shift($bits);
-            if (!css_is_width($width)) {
+            if (!css_style_borderwidth::is_border_width($width)) {
                 $width = '0';
             }
             $return[] = new css_style_borderwidth('border-top-width', $width);
@@ -2263,20 +2731,44 @@ class css_style_border extends css_style {
 
         foreach ($styles as $style) {
             switch ($style->get_name()) {
-                case 'border-top-width': $borderwidths['top'] = $style->get_value(); break;
-                case 'border-right-width': $borderwidths['right'] = $style->get_value(); break;
-                case 'border-bottom-width': $borderwidths['bottom'] = $style->get_value(); break;
-                case 'border-left-width': $borderwidths['left'] = $style->get_value(); break;
+                case 'border-top-width':
+                    $borderwidths['top'] = $style->get_value();
+                    break;
+                case 'border-right-width':
+                    $borderwidths['right'] = $style->get_value();
+                    break;
+                case 'border-bottom-width':
+                    $borderwidths['bottom'] = $style->get_value();
+                    break;
+                case 'border-left-width':
+                    $borderwidths['left'] = $style->get_value();
+                    break;
 
-                case 'border-top-style': $borderstyles['top'] = $style->get_value(); break;
-                case 'border-right-style': $borderstyles['right'] = $style->get_value(); break;
-                case 'border-bottom-style': $borderstyles['bottom'] = $style->get_value(); break;
-                case 'border-left-style': $borderstyles['left'] = $style->get_value(); break;
+                case 'border-top-style':
+                    $borderstyles['top'] = $style->get_value();
+                    break;
+                case 'border-right-style':
+                    $borderstyles['right'] = $style->get_value();
+                    break;
+                case 'border-bottom-style':
+                    $borderstyles['bottom'] = $style->get_value();
+                    break;
+                case 'border-left-style':
+                    $borderstyles['left'] = $style->get_value();
+                    break;
 
-                case 'border-top-color': $bordercolors['top'] = $style->get_value(); break;
-                case 'border-right-color': $bordercolors['right'] = $style->get_value(); break;
-                case 'border-bottom-color': $bordercolors['bottom'] = $style->get_value(); break;
-                case 'border-left-color': $bordercolors['left'] = $style->get_value(); break;
+                case 'border-top-color':
+                    $bordercolors['top'] = css_style_color::shrink_value($style->get_value());
+                    break;
+                case 'border-right-color':
+                    $bordercolors['right'] = css_style_color::shrink_value($style->get_value());
+                    break;
+                case 'border-bottom-color':
+                    $bordercolors['bottom'] = css_style_color::shrink_value($style->get_value());
+                    break;
+                case 'border-left-color':
+                    $bordercolors['left'] = css_style_color::shrink_value($style->get_value());
+                    break;
             }
         }
 
@@ -2481,9 +2973,15 @@ class css_style_border extends css_style {
         } else if (!is_null($width) && is_null($style) && is_null($color)) {
             $array[] = new $class($cssstyle, $width);
         } else {
-            if (!is_null($width)) $array[] = new $class($cssstyle, $width);
-            if (!is_null($style)) $array[] = new $class($cssstyle, $style);
-            if (!is_null($color)) $array[] = new $class($cssstyle, $color);
+            if (!is_null($width)) {
+                $array[] = new $class($cssstyle, $width);
+            }
+            if (!is_null($style)) {
+                $array[] = new $class($cssstyle, $style);
+            }
+            if (!is_null($color)) {
+                $array[] = new $class($cssstyle, $color);
+            }
         }
         return true;
     }
@@ -2492,7 +2990,7 @@ class css_style_border extends css_style {
 /**
  * A border colour style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2572,7 +3070,7 @@ class css_style_bordercolor extends css_style_color {
 /**
  * A border left style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2615,7 +3113,7 @@ class css_style_borderleft extends css_style_generic {
 /**
  * A border right style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2658,7 +3156,7 @@ class css_style_borderright extends css_style_generic {
 /**
  * A border top style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2701,7 +3199,7 @@ class css_style_bordertop extends css_style_generic {
 /**
  * A border bottom style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2744,7 +3242,7 @@ class css_style_borderbottom extends css_style_generic {
 /**
  * A border width style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2792,12 +3290,46 @@ class css_style_borderwidth extends css_style_width {
     public function consolidate_to() {
         return 'border';
     }
+
+    /**
+     * Checks if the width is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return self::is_border_width($this->value);
+    }
+
+    /**
+     * Cleans the provided value
+     *
+     * @param mixed $value Cleans the provided value optimising it if possible
+     * @return string
+     */
+    protected function clean_value($value) {
+        $isvalid = self::is_border_width($value);
+        if (!$isvalid) {
+            $this->set_error('Invalid width specified for '.$this->name);
+        } else if (preg_match('#^0\D+$#', $value)) {
+            return '0';
+        }
+        return trim($value);
+    }
+
+    /**
+     * Returns true if the provided value is a permitted border width
+     * @param string $value The value to check
+     * @return bool
+     */
+    public static function is_border_width($value) {
+        $altwidthvalues = array('thin', 'medium', 'thick');
+        return css_is_width($value) || in_array($value, $altwidthvalues);
+    }
 }
 
 /**
  * A border style style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2850,7 +3382,7 @@ class css_style_borderstyle extends css_style_generic {
 /**
  * A border top colour style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2880,7 +3412,7 @@ class css_style_bordertopcolor extends css_style_bordercolor {
 /**
  * A border left colour style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2910,7 +3442,7 @@ class css_style_borderleftcolor extends css_style_bordercolor {
 /**
  * A border right colour style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2940,7 +3472,7 @@ class css_style_borderrightcolor extends css_style_bordercolor {
 /**
  * A border bottom colour style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -2970,7 +3502,7 @@ class css_style_borderbottomcolor extends css_style_bordercolor {
 /**
  * A border width top style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3000,7 +3532,7 @@ class css_style_bordertopwidth extends css_style_borderwidth {
 /**
  * A border width left style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3030,7 +3562,7 @@ class css_style_borderleftwidth extends css_style_borderwidth {
 /**
  * A border width right style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3060,7 +3592,7 @@ class css_style_borderrightwidth extends css_style_borderwidth {
 /**
  * A border width bottom style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3090,7 +3622,7 @@ class css_style_borderbottomwidth extends css_style_borderwidth {
 /**
  * A border top style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3120,7 +3652,7 @@ class css_style_bordertopstyle extends css_style_borderstyle {
 /**
  * A border left style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3150,7 +3682,7 @@ class css_style_borderleftstyle extends css_style_borderstyle {
 /**
  * A border right style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3180,7 +3712,7 @@ class css_style_borderrightstyle extends css_style_borderstyle {
 /**
  * A border bottom style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3190,6 +3722,7 @@ class css_style_borderbottomstyle extends css_style_borderstyle {
     /**
      * Initialises this style object
      *
+     * @param string $value The value for the style
      * @return css_style_borderbottomstyle
      */
     public static function init($value) {
@@ -3209,7 +3742,7 @@ class css_style_borderbottomstyle extends css_style_borderstyle {
 /**
  * A background style
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3219,7 +3752,7 @@ class css_style_background extends css_style {
     /**
      * Initialises a background style
      *
-     * @param type $value The value of the style
+     * @param string $value The value of the style
      * @return array An array of background component.
      */
     public static function init($value) {
@@ -3231,57 +3764,126 @@ class css_style_background extends css_style {
             $value = str_replace($matches[1], '', $value);
         }
 
+        // Switch out the brackets so that they don't get messed up when we explode
+        $brackets = array();
+        $bracketcount = 0;
+        while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
+            $key = "##BRACKET-{$bracketcount}##";
+            $bracketcount++;
+            $brackets[$key] = $matches[0];
+            $value = str_replace($matches[0], $key, $value);
+        }
+
+        $important = (stripos($value, '!important') !== false);
+        if ($important) {
+            // Great some genius put !important in the background shorthand property
+            $value = str_replace('!important', '', $value);
+        }
+
         $value = preg_replace('#\s+#', ' ', $value);
         $bits = explode(' ', $value);
 
+        foreach ($bits as $key => $bit) {
+            $bits[$key] = self::replace_bracket_placeholders($bit, $brackets);
+        }
+        unset($bracketcount);
+        unset($brackets);
+
         $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
         $attachments = array('scroll' , 'fixed', 'inherit');
+        $positions = array('top', 'left', 'bottom', 'right', 'center');
 
         $return = array();
         $unknownbits = array();
 
+        $color = self::NULL_VALUE;
         if (count($bits) > 0 && css_is_colour(reset($bits))) {
-            $return[] = new css_style_backgroundcolor('background-color', array_shift($bits));
+            $color = array_shift($bits);
         }
-        if (count($bits) > 0 && preg_match('#(none|inherit|url\(\))#', reset($bits))) {
+
+        $image = self::NULL_VALUE;
+        if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) {
             $image = array_shift($bits);
             if ($image == 'url()') {
                 $image = "url({$imageurl})";
             }
-            $return[] = new css_style_backgroundimage('background-image', $image);
         }
+
+        $repeat = self::NULL_VALUE;
         if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
-            $return[] = new css_style_backgroundrepeat('background-repeat', array_shift($bits));
+            $repeat = array_shift($bits);
         }
+
+        $attachment = self::NULL_VALUE;
         if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
             // scroll , fixed, inherit
-            $return[] = new css_style_backgroundattachment('background-attachment', array_shift($bits));
+            $attachment = array_shift($bits);
         }
+
+        $position = self::NULL_VALUE;
         if (count($bits) > 0) {
             $widthbits = array();
             foreach ($bits as $bit) {
-                if (in_array($bit, array('top', 'left', 'bottom', 'right', 'center')) || css_is_width($bit)) {
+                if (in_array($bit, $positions) || css_is_width($bit)) {
                     $widthbits[] = $bit;
                 } else {
                     $unknownbits[] = $bit;
                 }
             }
-            $return[] = new css_style_backgroundposition('background-position', join(' ',$widthbits));
+            if (count($widthbits)) {
+                $position = join(' ', $widthbits);
+            }
         }
+
         if (count($unknownbits)) {
             foreach ($unknownbits as $bit) {
-                if (css_is_colour($bit)) {
-                    $return[] = new css_style_backgroundcolor('background-color', $bit);
-                } else if (in_array($bit, $repeats)) {
-                    $return[] = new css_style_backgroundrepeat('background-repeat', $bit);
-                } else if (in_array($bit, $attachments)) {
-                    $return[] = new css_style_backgroundattachment('background-attachment', $bit);
+                $bit = trim($bit);
+                if ($color === self::NULL_VALUE && css_is_colour($bit)) {
+                    $color = $bit;
+                } else if ($repeat === self::NULL_VALUE && in_array($bit, $repeats)) {
+                    $repeat = $bit;
+                } else if ($attachment === self::NULL_VALUE && in_array($bit, $attachments)) {
+                    $attachment = $bit;
+                } else if ($bit !== '') {
+                    $return[] = css_style_background_advanced::init($bit);
                 }
             }
         }
+
+        if ($color === self::NULL_VALUE && $image === self::NULL_VALUE && $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE && $position === self::NULL_VALUE) {
+            // All primaries are null, return without doing anything else. There may be advanced madness there.
+            return $return;
+        }
+
+        $return[] = new css_style_backgroundcolor('background-color', $color);
+        $return[] = new css_style_backgroundimage('background-image', $image);
+        $return[] = new css_style_backgroundrepeat('background-repeat', $repeat);
+        $return[] = new css_style_backgroundattachment('background-attachment', $attachment);
+        $return[] = new css_style_backgroundposition('background-position', $position);
+
+        if ($important) {
+            foreach ($return as $style) {
+                $style->set_important();
+            }
+        }
+
         return $return;
     }
 
+    /**
+     * Static helper method to switch in bracket replacements
+     *
+     * @param string $value
+     * @param array $placeholders
+     * @return string
+     */
+    protected static function replace_bracket_placeholders($value, array $placeholders) {
+        while (preg_match('/##BRACKET-\d+##/', $value, $matches)) {
+            $value = str_replace($matches[0], $placeholders[$matches[0]], $value);
+        }
+        return $value;
+    }
+
     /**
      * Consolidates background styles into a single background style
      *
@@ -3290,32 +3892,160 @@ class css_style_background extends css_style {
      */
     public static function consolidate(array $styles) {
 
-        if (count($styles) < 1) {
+        if (empty($styles)) {
             return $styles;
         }
 
-        $color = $image = $repeat = $attachment = $position = null;
+        $color = null;
+        $image = null;
+        $repeat = null;
+        $attachment = null;
+        $position = null;
+        $size = null;
+        $origin = null;
+        $clip = null;
+
+        $someimportant = false;
+        $allimportant = null;
+        foreach ($styles as $style) {
+            if ($style instanceof css_style_backgroundimage_advanced) {
+                continue;
+            }
+            if ($style->is_important()) {
+                $someimportant = true;
+                if ($allimportant === null) {
+                    $allimportant = true;
+                }
+            } else if ($allimportant === true) {
+                $allimportant = false;
+            }
+        }
+
+        $organisedstyles = array();
+        $advancedstyles = array();
+        $importantstyles = array();
         foreach ($styles as $style) {
+            if ($style instanceof css_style_backgroundimage_advanced) {
+                $advancedstyles[] = $style;
+                continue;
+            }
+            if ($someimportant && !$allimportant && $style->is_important()) {
+                $importantstyles[] = $style;
+                continue;
+            }
+            $organisedstyles[$style->get_name()] = $style;
             switch ($style->get_name()) {
-                case 'background-color' : $color = css_style_color::shrink_value($style->get_value()); break;
-                case 'background-image' : $image = $style->get_value(); break;
-                case 'background-repeat' : $repeat = $style->get_value(); break;
-                case 'background-attachment' : $attachment = $style->get_value(); break;
-                case 'background-position' : $position = $style->get_value(); break;
+                case 'background-color' :
+                    $color = css_style_color::shrink_value($style->get_value(false));
+                    break;
+                case 'background-image' :
+                    $image = $style->get_value(false);
+                    break;
+                case 'background-repeat' :
+                    $repeat = $style->get_value(false);
+                    break;
+                case 'background-attachment' :
+                    $attachment = $style->get_value(false);
+                    break;
+                case 'background-position' :
+                    $position = $style->get_value(false);
+                    break;
+                case 'background-clip' :
+                    $clip = $style->get_value();
+                    break;
+                case 'background-origin' :
+                    $origin = $style->get_value();
+                    break;
+                case 'background-size' :
+                    $size = $style->get_value();
+                    break;
             }
         }
 
-        if ((is_null($image) || is_null($position) || is_null($repeat)) && ($image!= null || $position != null || $repeat != null)) {
-            return $styles;
+        $consolidatetosingle = array();
+        if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
+            // We can use the shorthand background-style!
+            if (!$organisedstyles['background-color']->is_special_empty_value()) {
+                $consolidatetosingle[] = $color;
+            }
+            if (!$organisedstyles['background-image']->is_special_empty_value()) {
+                $consolidatetosingle[] = $image;
+            }
+            if (!$organisedstyles['background-repeat']->is_special_empty_value()) {
+                $consolidatetosingle[] = $repeat;
+            }
+            if (!$organisedstyles['background-attachment']->is_special_empty_value()) {
+                $consolidatetosingle[] = $attachment;
+            }
+            if (!$organisedstyles['background-position']->is_special_empty_value()) {
+                $consolidatetosingle[] = $position;
+            }
+            // Reset them all to null so we don't use them again.
+            $color = null;
+            $image = null;
+            $repeat = null;
+            $attachment = null;
+            $position = null;
         }
 
-        $value = array();
-        if (!is_null($color)) $value[] .= $color;
-        if (!is_null($image)) $value[] .= $image;
-        if (!is_null($repeat)) $value[] .= $repeat;
-        if (!is_null($attachment)) $value[] .= $attachment;
-        if (!is_null($position)) $value[] .= $position;
-        return array(new css_style_background('background', join(' ', $value)));
+        $return = array();
+        // Single background style needs to come first;
+        if (count($consolidatetosingle) > 0) {
+            $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
+            if ($allimportant) {
+                $returnstyle->set_important();
+            }
+            $return[] = $returnstyle;
+        }
+        foreach ($styles as $style) {
+            $value = null;
+            switch ($style->get_name()) {
+                case 'background-color'      : $value = $color;      break;
+                case 'background-image'      : $value = $image;      break;
+                case 'background-repeat'     : $value = $repeat;     break;
+                case 'background-attachment' : $value = $attachment; break;
+                case 'background-position'   : $value = $position;   break;
+                case 'background-clip'       : $value = $clip;       break;
+                case 'background-origin'     : $value = $origin;     break;
+                case 'background-size'       : $value = $size;       break;
+            }
+            if (!is_null($value)) {
+                $return[] = $style;
+            }
+        }
+        $return = array_merge($return, $importantstyles, $advancedstyles);
+        return $return;
+    }
+}
+
+/**
+ * A advanced background style that allows multiple values to preserve unknown entities
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_background_advanced extends css_style_generic {
+    /**
+     * Creates a new background colour style
+     *
+     * @param string $value The value of the style
+     * @return css_style_backgroundimage
+     */
+    public static function init($value) {
+        $value = preg_replace('#\s+#', ' ', $value);
+        return new css_style_background_advanced('background', $value);
+    }
+
+    /**
+     * Returns true because the advanced background image supports multiple values.
+     * e.g. -webkit-linear-gradient and -moz-linear-gradient.
+     *
+     * @return boolean
+     */
+    public function allows_multiple_values() {
+        return true;
     }
 }
 
@@ -3324,7 +4054,7 @@ class css_style_background extends css_style {
  *
  * Based upon the colour style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3349,12 +4079,33 @@ class css_style_backgroundcolor extends css_style_color {
     public function consolidate_to() {
         return 'background';
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This occurs if the shorthand background property was used but no proper value
+     * was specified for this style.
+     * This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return ($this->value === self::NULL_VALUE);
+    }
+
+    /**
+     * Returns true if the value for this style is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return $this->is_special_empty_value() || parent::is_valid();
+    }
 }
 
 /**
  * A background image style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3362,12 +4113,15 @@ class css_style_backgroundcolor extends css_style_color {
 class css_style_backgroundimage extends css_style_generic {
 
     /**
-     * Creates a new background colour style
+     * Creates a new background image style
      *
      * @param string $value The value of the style
      * @return css_style_backgroundimage
      */
     public static function init($value) {
+        if (!preg_match('#^\s*(none|inherit|url\()#i', $value)) {
+            return css_style_backgroundimage_advanced::init($value);
+        }
         return new css_style_backgroundimage('background-image', $value);
     }
 
@@ -3379,12 +4133,64 @@ class css_style_backgroundimage extends css_style_generic {
     public function consolidate_to() {
         return 'background';
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This occurs if the shorthand background property was used but no proper value
+     * was specified for this style.
+     * This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return ($this->value === self::NULL_VALUE);
+    }
+
+    /**
+     * Returns true if the value for this style is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return $this->is_special_empty_value() || parent::is_valid();
+    }
+}
+
+/**
+ * A background image style that supports mulitple values and masquerades as a background-image
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_backgroundimage_advanced extends css_style_generic {
+    /**
+     * Creates a new background colour style
+     *
+     * @param string $value The value of the style
+     * @return css_style_backgroundimage
+     */
+    public static function init($value) {
+        $value = preg_replace('#\s+#', ' ', $value);
+        return new css_style_backgroundimage_advanced('background-image', $value);
+    }
+
+    /**
+     * Returns true because the advanced background image supports multiple values.
+     * e.g. -webkit-linear-gradient and -moz-linear-gradient.
+     *
+     * @return boolean
+     */
+    public function allows_multiple_values() {
+        return true;
+    }
 }
 
 /**
  * A background repeat style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3409,12 +4215,33 @@ class css_style_backgroundrepeat extends css_style_generic {
     public function consolidate_to() {
         return 'background';
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This occurs if the shorthand background property was used but no proper value
+     * was specified for this style.
+     * This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return ($this->value === self::NULL_VALUE);
+    }
+
+    /**
+     * Returns true if the value for this style is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return $this->is_special_empty_value() || parent::is_valid();
+    }
 }
 
 /**
  * A background attachment style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3439,12 +4266,33 @@ class css_style_backgroundattachment extends css_style_generic {
     public function consolidate_to() {
         return 'background';
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This occurs if the shorthand background property was used but no proper value
+     * was specified for this style.
+     * This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return ($this->value === self::NULL_VALUE);
+    }
+
+    /**
+     * Returns true if the value for this style is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return $this->is_special_empty_value() || parent::is_valid();
+    }
 }
 
 /**
  * A background position style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3469,12 +4317,123 @@ class css_style_backgroundposition extends css_style_generic {
     public function consolidate_to() {
         return 'background';
     }
+
+    /**
+     * Returns true if the value for this style is the special null value.
+     *
+     * This occurs if the shorthand background property was used but no proper value
+     * was specified for this style.
+     * This leads to a null value being used unless otherwise overridden.
+     *
+     * @return bool
+     */
+    public function is_special_empty_value() {
+        return ($this->value === self::NULL_VALUE);
+    }
+
+    /**
+     * Returns true if the value for this style is valid
+     * @return bool
+     */
+    public function is_valid() {
+        return $this->is_special_empty_value() || parent::is_valid();
+    }
+}
+
+/**
+ * A background size style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_backgroundsize extends css_style_generic {
+
+    /**
+     * Creates a new background size style
+     *
+     * @param string $value The value of the style
+     * @return css_style_backgroundposition
+     */
+    public static function init($value) {
+        return new css_style_backgroundsize('background-size', $value);
+    }
+
+    /**
+     * Consolidates this style into a single background style
+     *
+     * @return string
+     */
+    public function consolidate_to() {
+        return 'background';
+    }
+}
+
+/**
+ * A background clip style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_backgroundclip extends css_style_generic {
+
+    /**
+     * Creates a new background clip style
+     *
+     * @param string $value The value of the style
+     * @return css_style_backgroundposition
+     */
+    public static function init($value) {
+        return new css_style_backgroundclip('background-clip', $value);
+    }
+
+    /**
+     * Consolidates this style into a single background style
+     *
+     * @return string
+     */
+    public function consolidate_to() {
+        return 'background';
+    }
+}
+
+/**
+ * A background origin style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_backgroundorigin extends css_style_generic {
+
+    /**
+     * Creates a new background origin style
+     *
+     * @param string $value The value of the style
+     * @return css_style_backgroundposition
+     */
+    public static function init($value) {
+        return new css_style_backgroundorigin('background-origin', $value);
+    }
+
+    /**
+     * Consolidates this style into a single background style
+     *
+     * @return string
+     */
+    public function consolidate_to() {
+        return 'background';
+    }
 }
 
 /**
  * A padding style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3528,25 +4487,72 @@ class css_style_padding extends css_style_width {
         if (count($styles) != 4) {
             return $styles;
         }
-        $top = $right = $bottom = $left = null;
+
+        $someimportant = false;
+        $allimportant = null;
+        $notimportantequal = null;
+        $firstvalue = null;
         foreach ($styles as $style) {
-            switch ($style->get_name()) {
-                case 'padding-top' : $top = $style->get_value();break;
-                case 'padding-right' : $right = $style->get_value();break;
-                case 'padding-bottom' : $bottom = $style->get_value();break;
-                case 'padding-left' : $left = $style->get_value();break;
+            if ($style->is_important()) {
+                $someimportant = true;
+                if ($allimportant === null) {
+                    $allimportant = true;
+                }
+            } else {
+                if ($allimportant === true) {
+                    $allimportant = false;
+                }
+                if ($firstvalue == null) {
+                    $firstvalue = $style->get_value(false);
+                    $notimportantequal = true;
+                } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
+                    $notimportantequal = false;
+                }
             }
         }
-        if ($top == $bottom && $left == $right) {
-            if ($top == $left) {
-                return array(new css_style_padding('padding', $top));
-            } else {
-                return array(new css_style_padding('padding', "{$top} {$left}"));
+
+        if ($someimportant && !$allimportant && !$notimportantequal) {
+            return $styles;
+        }
+
+        if ($someimportant && !$allimportant && $notimportantequal) {
+            $return = array(
+                new css_style_padding('padding', $firstvalue)
+            );
+            foreach ($styles as $style) {
+                if ($style->is_important()) {
+                    $return[] = $style;
+                }
             }
-        } else if ($left == $right) {
-            return array(new css_style_padding('padding', "{$top} {$right} {$bottom}"));
+            return $return;
         } else {
-            return array(new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}"));
+            $top = null;
+            $right = null;
+            $bottom = null;
+            $left = null;
+            foreach ($styles as $style) {
+                switch ($style->get_name()) {
+                    case 'padding-top' : $top = $style->get_value(false);break;
+                    case 'padding-right' : $right = $style->get_value(false);break;
+                    case 'padding-bottom' : $bottom = $style->get_value(false);break;
+                    case 'padding-left' : $left = $style->get_value(false);break;
+                }
+            }
+            if ($top == $bottom && $left == $right) {
+                if ($top == $left) {
+                    $returnstyle = new css_style_padding('padding', $top);
+                } else {
+                    $returnstyle = new css_style_padding('padding', "{$top} {$left}");
+                }
+            } else if ($left == $right) {
+                $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}");
+            } else {
+                $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}");
+            }
+            if ($allimportant) {
+                $returnstyle->set_important();
+            }
+            return array($returnstyle);
         }
     }
 }
@@ -3554,7 +4560,7 @@ class css_style_padding extends css_style_width {
 /**
  * A padding top style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3584,7 +4590,7 @@ class css_style_paddingtop extends css_style_padding {
 /**
  * A padding right style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3614,7 +4620,7 @@ class css_style_paddingright extends css_style_padding {
 /**
  * A padding bottom style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3644,7 +4650,7 @@ class css_style_paddingbottom extends css_style_padding {
 /**
  * A padding left style.
  *
- * @package core_css
+ * @package core
  * @category css
  * @copyright 2012 Sam Hemelryk
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -3670,3 +4676,102 @@ class css_style_paddingleft extends css_style_padding {
         return 'padding';
     }
 }
+
+/**
+ * A cursor style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_cursor extends css_style_generic {
+    /**
+     * Initialises a new cursor style
+     * @param string $value
+     * @return css_style_cursor
+     */
+    public static function init($value) {
+        return new css_style_cursor('cursor', $value);
+    }
+    /**
+     * Cleans the given value and returns it.
+     *
+     * @param string $value
+     * @return string
+     */
+    protected function clean_value($value) {
+        // Allowed values for the cursor style
+        $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
+                         'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
+        // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough
+        if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
+            $this->set_error('Invalid or unexpected cursor value specified: '.$value);
+        }
+        return trim($value);
+    }
+}
+
+/**
+ * A vertical alignment style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_verticalalign extends css_style_generic {
+    /**
+     * Initialises a new vertical alignment style
+     * @param string $value
+     * @return css_style_verticalalign
+     */
+    public static function init($value) {
+        return new css_style_verticalalign('vertical-align', $value);
+    }
+    /**
+     * Cleans the given value and returns it.
+     *
+     * @param string $value
+     * @return string
+     */
+    protected function clean_value($value) {
+        $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
+        if (!css_is_width($value) && !in_array($value, $allowed)) {
+            $this->set_error('Invalid vertical-align value specified: '.$value);
+        }
+        return trim($value);
+    }
+}
+
+/**
+ * A float style.
+ *
+ * @package core
+ * @category css
+ * @copyright 2012 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class css_style_float extends css_style_generic {
+    /**
+     * Initialises a new float style
+     * @param string $value
+     * @return css_style_float
+     */
+    public static function init($value) {
+        return new css_style_float('float', $value);
+    }
+    /**
+     * Cleans the given value and returns it.
+     *
+     * @param string $value
+     * @return string
+     */
+    protected function clean_value($value) {
+        $allowed = array('left', 'right', 'none', 'inherit');
+        if (!css_is_width($value) && !in_array($value, $allowed)) {
+            $this->set_error('Invalid float value specified: '.$value);
+        }
+        return trim($value);
+    }
+}
index 3d8b8cd..0d18cfa 100644 (file)
@@ -773,6 +773,7 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea
         $newhashes = array();
         foreach ($draftfiles as $file) {
             $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
+            file_restore_source_field_from_draft_file($file);
             $newhashes[$newhash] = $file;
         }
         $filecount = 0;
@@ -793,7 +794,6 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea
                 continue;
             }
 
-            file_restore_source_field_from_draft_file($newfile);
             // Replaced file content
             if ($oldfile->get_contenthash() != $newfile->get_contenthash()) {
                 $oldfile->replace_content_with($newfile);
index 62e68c1..ca75b0d 100644 (file)
@@ -222,6 +222,29 @@ class filter_manager {
         }
         return implode('-', $hashes);
     }
+
+    /**
+     * Setup page with filters requirements and other prepare stuff.
+     *
+     * This method is used by {@see format_text()} and {@see format_string()}
+     * in order to allow filters to setup any page requirement (js, css...)
+     * or perform any action needed to get them prepared before filtering itself
+     * happens by calling to each every active setup() method.
+     *
+     * Note it's executed for each piece of text filtered, so filter implementations
+     * are responsible of controlling the cardinality of the executions that may
+     * be different depending of the stuff to prepare.
+     *
+     * @param moodle_page $page the page we are going to add requirements to.
+     * @param context $context the context which contents are going to be filtered.
+     * @since 2.3
+     */
+    public function setup_page_for_filters($page, $context) {
+        $filters = $this->get_text_filters($context);
+        foreach ($filters as $filter) {
+            $filter->setup($page, $context);
+        }
+    }
 }
 
 /**
@@ -357,6 +380,24 @@ abstract class moodle_text_filter {
         return __CLASS__;
     }
 
+    /**
+     * Setup page with filter requirements and other prepare stuff.
+     *
+     * Override this method if the filter needs to setup page
+     * requirements or needs other stuff to be executed.
+     *
+     * Note this method is invoked from {@see setup_page_for_filters()}
+     * for each piece of text being filtered, so it is responsible
+     * for controlling its own execution cardinality.
+     *
+     * @param moodle_page $page the page we are going to add requirements to.
+     * @param context $context the context which contents are going to be filtered.
+     * @since 2.3
+     */
+    public function setup($page, $context) {
+        // Override me, if needed.
+    }
+
     /**
      * Override this function to actually implement the filtering.
      *
index 132cbf7..e433d35 100644 (file)
@@ -328,7 +328,6 @@ M.form_filemanager.init = function(Y, options) {
             }
 
             // setup 'download this folder' button
-            // NOTE: popup window must be enabled to perform download process
             button_download.on('click',function(e) {
                 e.preventDefault();
                 var scope = this;
@@ -733,7 +732,13 @@ M.form_filemanager.init = function(Y, options) {
             selectnode.one('.fp-file-download').on('click', function(e) {
                 e.preventDefault();
                 if (this.selectui.fileinfo.type != 'folder') {
-                    window.open(this.selectui.fileinfo.url, 'fm-download-file');
+                    node = Y.Node.create('<iframe></iframe>').setStyles({
+                        visibility : 'hidden',
+                        width : '1px',
+                        height : '1px'
+                    });
+                    node.set('src', this.selectui.fileinfo.url);
+                    Y.one('body').appendChild(node);
                 }
             }, this);
             selectnode.one('.fp-file-delete').on('click', function(e) {
index 708c613..6978a42 100644 (file)
@@ -5860,9 +5860,10 @@ function get_user_max_upload_file_size($context, $sitebytes=0, $coursebytes=0, $
         $user = $USER;
     }
 
-    if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
-        return -1;
-    }
+    // Temp. commenting this until MDL-27156 fixes it!
+    // if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
+    //     return -1;
+    // }
 
     return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
 }
similarity index 52%
rename from lib/tests/cssslib_test.php
rename to lib/tests/csslib_test.php
index 2faf911..c5f1223 100644 (file)
@@ -39,6 +39,15 @@ require_once($CFG->libdir . '/csslib.php');
  */
 class css_optimiser_testcase extends advanced_testcase {
 
+    protected $optimiser;
+
+    public function get_optimiser() {
+        if (!$this->optimiser instanceof css_optimiser) {
+            $this->optimiser = new css_optimiser;
+        }
+        return $this->optimiser;
+    }
+
     /**
      * Sets up the test class
      */
@@ -59,26 +68,51 @@ class css_optimiser_testcase extends advanced_testcase {
     public function test_process() {
         $optimiser = new css_optimiser;
 
-        $this->check_background($optimiser);
-        $this->check_borders($optimiser);
-        $this->check_colors($optimiser);
-        $this->check_margins($optimiser);
-        $this->check_padding($optimiser);
-        $this->check_widths($optimiser);
-
-        $this->try_broken_css_found_in_moodle($optimiser);
-        $this->try_invalid_css_handling($optimiser);
-        $this->try_bulk_processing($optimiser);
-        $this->try_break_things($optimiser);
+        $this->try_broken_css_found_in_moodle();
+        $this->try_invalid_css_handling();
+        $this->try_bulk_processing();
+        $this->try_break_things();
+        $this->try_media_rules();
+        $this->try_keyframe_css_animation();
     }
 
     /**
      * Background colour tests
+     *
+     * When testing background styles it is important to understand how the background shorthand works.
+     * background shorthand allows the following styles to be specified in a single "background" declaration:
+     *   - background-color
+     *   - background-image
+     *   - background-repeat
+     *   - background-attachment
+     *   - background-position
+     *
+     * If the background shorthand is used it can contain one or more of those (preferabbly in that order).
+     * Important it does not need to contain all of them.
+     * However even if it doesn't contain values for all styles all of the styles will be affected.
+     * If a style is missed from the shorthand background style but has an existing value in the rule then the existing value
+     * is cleared.
+     *
+     * For example:
+     *      .test {background: url(\'test.png\');background: bottom right;background:#123456;}
+     * will result in:
+     *      .test {background:#123456;}
+     *
+     * And:
+     *      .test {background-image: url(\'test.png\');background:#123456;}
+     * will result in:
+     *      .test {background:#123456;}
+     *
      * @param css_optimiser $optimiser
      */
-    protected function check_background(css_optimiser $optimiser) {
+    public function test_background() {
+        $optimiser = $this->get_optimiser();
 
         $cssin = '.test {background-color: #123456;}';
+        $cssout = '.test{background-color:#123456;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test {background: #123456;}';
         $cssout = '.test{background:#123456;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
@@ -90,12 +124,21 @@ class css_optimiser_testcase extends advanced_testcase {
         $cssout = '.test{background:#123456 url(\'test.png\') no-repeat top left;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
+        // Check out this for madness, background position and background-repeat have been reversed
+        $cssin = '.test {background: #123456 url(\'test.png\') center no-repeat;}';
+        $cssout = '.test{background:#123456 url(\'test.png\') no-repeat center;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
         $cssin = '.test {background: url(\'test.png\') no-repeat top left;}.test{background-position: bottom right}.test {background-color:#123456;}';
         $cssout = '.test{background:#123456 url(\'test.png\') no-repeat bottom right;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.test {background: url(   \'test.png\'    )}.test{background: bottom right}.test {background:#123456;}';
-        $cssout = '.test{background-image:url(\'test.png\');background-position:bottom right;background-color:#123456;}';
+        $cssout = '.test{background:#123456;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test {background-image: url(\'test.png\');background:#123456;}';
+        $cssout = '.test{background:#123456;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.test {background-color: #123456;background-repeat: repeat-x; background-position: 100% 0%;}';
@@ -104,17 +147,11 @@ class css_optimiser_testcase extends advanced_testcase {
 
         $cssin = '.tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
                   .tree_item.branch.navigation_node {background-image:none;padding-left:0;}';
-        $cssout = '.tree_item.branch{background:url([[pix:t/expanded]]) no-repeat 0 10%;} .tree_item.branch.navigation_node{background-image:none;padding-left:0;}';
+        $cssout = '.tree_item.branch{background-image:url([[pix:t/expanded]]);background-position:0 10%;background-repeat:no-repeat;} .tree_item.branch.navigation_node{background-image:none;padding-left:0;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0% 5%;background-repeat: no-repeat;}
-                  .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}';
-        $cssout = '.block_tree .tree_item.emptybranch{background:url([[pix:t/collapsed_empty]]) no-repeat 0% 5%;} .block_tree .collapsed .tree_item.branch{background-image:url([[pix:t/collapsed]]);}';
-        $this->assertEquals($cssout, $optimiser->process($cssin));
-
-
         $cssin = '#nextLink{background:url(data:image/gif;base64,AAAA);}';
-        $cssout = '#nextLink{background-image:url(data:image/gif;base64,AAAA);}';
+        $cssout = '#nextLink{background:url(data:image/gif;base64,AAAA);}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '#nextLink{background-image:url(data:image/gif;base64,AAAA);}';
@@ -124,47 +161,109 @@ class css_optimiser_testcase extends advanced_testcase {
         $cssin = '.test {background: #123456 url(data:image/gif;base64,AAAA) no-repeat top left;}';
         $cssout = '.test{background:#123456 url(data:image/gif;base64,AAAA) no-repeat top left;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '#test {background-image:none;background-position:right center;background-repeat:no-repeat;}';
+        $cssout = '#test{background-image:none;background-position:right center;background-repeat:no-repeat;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test {background: url([[pix:theme|photos]]) no-repeat 50% 50%;background-size: 40px 40px;-webkit-background-size: 40px 40px;}';
+        $cssout = '.test{background:url([[pix:theme|photos]]) no-repeat 50% 50%;background-size:40px 40px;-webkit-background-size:40px 40px;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test{background-image: -o-linear-gradient(#3c3c3c, #111);background-image: linear-gradient(#3c3c3c, #111);}';
+        $cssout = '.test{background-image:-o-linear-gradient(#3c3c3c, #111);background-image:linear-gradient(#3c3c3c, #111);}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test{background-image: -moz-linear-gradient(#3c3c3c, #111);background-image: -webkit-linear-gradient(#3c3c3c, #111);background-image: -o-linear-gradient(#3c3c3c, #111);background-image: linear-gradient(#3c3c3c, #111);background-image: url(/test.png);}';
+        $cssout = '.test{background-image:-moz-linear-gradient(#3c3c3c, #111);background-image:-webkit-linear-gradient(#3c3c3c, #111);background-image:-o-linear-gradient(#3c3c3c, #111);background-image:linear-gradient(#3c3c3c, #111);background-image:url(/test.png);}';
+        $cssout = '.test{background-image:url(/test.png);background-image:-moz-linear-gradient(#3c3c3c, #111);background-image:-webkit-linear-gradient(#3c3c3c, #111);background-image:-o-linear-gradient(#3c3c3c, #111);background-image:linear-gradient(#3c3c3c, #111);}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test{background:#CCC; background-image: url(test.png);}';
+        $cssout = '.test{background:#CCC url(test.png);}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test{background:#CCC; background-image: linear-gradient(#3c3c3c, #111);}';
+        $cssout = '.test{background:#CCC;background-image:linear-gradient(#3c3c3c, #111);}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.test{background:#CCC; background-image: -o-linear-gradient(#3c3c3c, #111);background-image: linear-gradient(#3c3c3c, #111);}';
+        $cssout = '.test{background:#CCC;background-image:-o-linear-gradient(#3c3c3c, #111);background-image:linear-gradient(#3c3c3c, #111);}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '#newmessageoverlay{font-weight: normal; border: 1px solid #222; background: #444; color: #ddd; text-shadow: 0 -1px 0px #000; background-image: -moz-linear-gradient(top, #333 0%, #333 5%, #444 15%, #444 60%, #222 100%); background-image: -webkit-gradient(linear, center top, center bottom, color-stop(0, #333), color-stop(5%, #333), color-stop(15%, #444), color-stop(60%, #444), color-stop(1, #222)); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=\'#333333\', EndColorStr=\'#222222\')"; padding:20px; padding-left: 0px; padding-right: 10px; position: inherit; z-index: 9999; width: 90%; margin-left: auto; margin-right: auto; height: 100%;}';
+        $cssout = '#newmessageoverlay{font-weight:normal;border:1px solid #222;background:#444;color:#DDD;text-shadow:0 -1px 0px #000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=\'#333333\', EndColorStr=\'#222222\')";padding:20px 10px 20px 0;position:inherit;z-index:9999;width:90%;margin-left:auto;margin-right:auto;height:100%;background-image:-moz-linear-gradient(top, #333 0%, #333 5%, #444 15%, #444 60%, #222 100%);background-image:-webkit-gradient(linear, center top, center bottom, color-stop(0, #333), color-stop(5%, #333), color-stop(15%, #444), color-stop(60%, #444), color-stop(1, #222));}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.userenrolment {background-color:inherit !important;background: inherit !important;}';
+        $cssout = '.userenrolment{background:inherit !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.userenrolment {background-image:url(test.png) !important;background: inherit !important;}';
+        $cssout = '.userenrolment{background:inherit !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.userenrolment {background: inherit !important;background-image:url(test.png) !important;}';
+        $cssout = '.userenrolment{background:inherit url(test.png) !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.userenrolment {background: inherit !important;background-image:url(test.png);}';
+        $cssout = '.userenrolment{background:inherit !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $css = '#filesskin .yui3-widget-hd{background:#CCC;background:-webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#CCCCCC));background:-moz-linear-gradient(top, #FFFFFF, #CCCCCC);}';
+        $this->assertEquals($css, $optimiser->process($css));
     }
 
     /**
      * Border tests
      * @param css_optimiser $optimiser
      */
-    protected function check_borders(css_optimiser $optimiser) {
+    public function test_borders() {
+        $optimiser = $this->get_optimiser();
+
         $cssin = '.test {border: 1px solid #654321} .test {border-bottom-color: #123456}';
         $cssout = '.test{border:1px solid;border-color:#654321 #654321 #123456;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {border:1px solid red;}';
-        $cssout = '.one{border:1px solid #FF0000;}';
+        $cssout = '.one{border:1px solid #F00;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {border:1px solid;} .one {border:2px dotted #DDD;}';
-        $cssout = '.one{border:2px dotted #DDDDDD;}';
+        $cssout = '.one{border:2px dotted #DDD;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {border:2px dotted #DDD;}.one {border:1px solid;} ';
-        $cssout = '.one{border:1px solid #DDDDDD;}';
+        $cssout = '.one{border:1px solid #DDD;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one, .two {border:1px solid red;}';
-        $cssout = ".one, .two{border:1px solid #FF0000;}";
+        $cssout = ".one, .two{border:1px solid #F00;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one, .two {border:0px;}';
         $cssout = ".one, .two{border-width:0;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
+        $cssin = '.one, .two {border: thin;}';
+        $cssout = ".one, .two{border-width:thin;}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.one, .two {border: thin solid black;}';
+        $cssout = ".one, .two{border:thin solid #000;}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
         $cssin = '.one, .two {border-top: 5px solid white;}';
-        $cssout = ".one, .two{border-top:5px solid #FFFFFF;}";
+        $cssout = ".one, .two{border-top:5px solid #FFF;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {border:1px solid red;} .two {border:1px solid red;}';
-        $cssout = ".one, .two{border:1px solid #FF0000;}";
+        $cssout = ".one, .two{border:1px solid #F00;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {border:1px solid red;width:20px;} .two {border:1px solid red;height:20px;}';
-        $cssout = ".one{width:20px;border:1px solid #FF0000;} .two{height:20px;border:1px solid #FF0000;}";
+        $cssout = ".one{border:1px solid #F00;width:20px;} .two{border:1px solid #F00;height:20px;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.test {border: 1px solid #123456;} .test {border-color: #654321}';
@@ -199,8 +298,8 @@ class css_optimiser_testcase extends advanced_testcase {
         $cssout = '.test{border:1px solid;border-color:#111 #444 #222 #333;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.generaltable .cell {border-color:#EEEEEE;} .generaltable .cell {border-width: 1px;border-style: solid;}';
-        $cssout = '.generaltable .cell{border:1px solid #EEEEEE;}';
+        $cssin = '.generaltable .cell {border-color:#EEE;} .generaltable .cell {border-width: 1px;border-style: solid;}';
+        $cssout = '.generaltable .cell{border:1px solid #EEE;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '#page-admin-roles-override .rolecap {border:none;border-bottom:1px solid #CECECE;}';
@@ -212,7 +311,9 @@ class css_optimiser_testcase extends advanced_testcase {
      * Test colour styles
      * @param css_optimiser $optimiser
      */
-    protected function check_colors(css_optimiser $optimiser) {
+    public function test_colors() {
+        $optimiser = $this->get_optimiser();
+
         $css = '.css{}';
         $this->assertEquals($css, $optimiser->process($css));
 
@@ -270,6 +371,10 @@ class css_optimiser_testcase extends advanced_testcase {
         $cssout = '.one{color:#123 !important;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
+        $cssin = '.one {color:#123!important;} .one {color:#321;}';
+        $cssout = '.one{color:#123 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
         $cssin = '.one {color:rgb(255, 128, 1)}';
         $cssout = '.one{color:rgb(255, 128, 1);}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
@@ -300,7 +405,9 @@ class css_optimiser_testcase extends advanced_testcase {
         $this->assertEquals($css, $optimiser->process($css));
     }
 
-    protected function check_widths(css_optimiser $optimiser) {
+    public function test_widths() {
+        $optimiser = $this->get_optimiser();
+
         $cssin  = '.css {width:0}';
         $cssout = '.css{width:0;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
@@ -330,7 +437,9 @@ class css_optimiser_testcase extends advanced_testcase {
      * Test margin styles
      * @param css_optimiser $optimiser
      */
-    protected function check_margins(css_optimiser $optimiser) {
+    public function test_margins() {
+        $optimiser = $this->get_optimiser();
+
         $cssin = '.one {margin: 1px 2px 3px 4px}';
         $cssout = '.one{margin:1px 2px 3px 4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
@@ -352,7 +461,19 @@ class css_optimiser_testcase extends advanced_testcase {
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one, .two, .one.two, .one .two {margin:0;} .one.two {margin:0 7px;}';
-        $cssout = '.one, .two, .one .two{margin:0;} .one.two{margin:0 7px;}';
+        $cssout = '.one, .two{margin:0;} .one.two{margin:0 7px;} .one .two{margin:0;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {margin-top: 0px !important;margin-bottom: 0px !important;}';
+        $cssout = '.block{margin-top:0 !important;margin-bottom:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {margin: 0px !important;margin-bottom: 3px;}';
+        $cssout = '.block{margin:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {margin: 5px;margin-right: 0 !important;}';
+        $cssout = '.block{margin:5px;margin-right:0 !important;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
     }
 
@@ -361,37 +482,125 @@ class css_optimiser_testcase extends advanced_testcase {
      *
      * @param css_optimiser $optimiser
      */
-    protected function check_padding(css_optimiser $optimiser) {
-        $cssin = '.one {margin: 1px 2px 3px 4px}';
-        $cssout = '.one{margin:1px 2px 3px 4px;}';
+    public function test_padding() {
+        $optimiser = $this->get_optimiser();
+
+        $cssin = '.one {padding: 1px 2px 3px 4px}';
+        $cssout = '.one{padding:1px 2px 3px 4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one {margin-top:1px; margin-left:4px; margin-right:2px; margin-bottom: 3px;}';
-        $cssout = '.one{margin:1px 2px 3px 4px;}';
+        $cssin = '.one {padding-top:1px; padding-left:4px; padding-right:2px; padding-bottom: 3px;}';
+        $cssout = '.one{padding:1px 2px 3px 4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one {margin-top:1px; margin-left:4px;}';
-        $cssout = '.one{margin-top:1px;margin-left:4px;}';
+        $cssin = '.one {padding-top:1px; padding-left:4px;padding-bottom: 3px;}';
+        $cssout = '.one{padding-top:1px;padding-left:4px;padding-bottom:3px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one {margin:1px; margin-left:4px;}';
-        $cssout = '.one{margin:1px 1px 1px 4px;}';
+        $cssin = '.one {padding-top:1px; padding-left:4px;}';
+        $cssout = '.one{padding-top:1px;padding-left:4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one {margin:1px; margin-bottom:4px;}';
-        $cssout = '.one{margin:1px 1px 4px;}';
+        $cssin = '.one {padding:1px; padding-left:4px;}';
+        $cssout = '.one{padding:1px 1px 1px 4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one {margin:0 !important;}';
-        $cssout = '.one{margin:0 !important;}';
+        $cssin = '.one {padding:1px; padding-bottom:4px;}';
+        $cssout = '.one{padding:1px 1px 4px;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {padding:0 !important;}';
         $cssout = '.one{padding:0 !important;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
-        $cssin = '.one, .two, .one.two, .one .two {margin:0;} .one.two {margin:0 7px;}';
-        $cssout = '.one, .two, .one .two{margin:0;} .one.two{margin:0 7px;}';
+        $cssin = '.one {padding:0 !important;}';
+        $cssout = '.one{padding:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.one, .two, .one.two, .one .two {padding:0;} .one.two {padding:0 7px;}';
+        $cssout = '.one, .two{padding:0;} .one.two{padding:0 7px;} .one .two{padding:0;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {padding-top: 0px !important;padding-bottom: 0px !important;}';
+        $cssout = '.block{padding-top:0 !important;padding-bottom:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {padding: 0px !important;padding-bottom: 3px;}';
+        $cssout = '.block{padding:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = '.block {padding: 5px;padding-right: 0 !important;}';
+        $cssout = '.block{padding:5px;padding-right:0 !important;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+
+    public function test_cursor() {
+        $optimiser = $this->get_optimiser();
+
+        // Valid cursor
+        $cssin = '.one {cursor: pointer;}';
+        $cssout = '.one{cursor:pointer;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        // Invalid cursor but tollerated
+        $cssin = '.one {cursor: hand;}';
+        $cssout = '.one{cursor:hand;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        // Valid cursor: url relative
+        $cssin = '.one {cursor: mycursor.png;}';
+        $cssout = '.one{cursor:mycursor.png;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        // Valid cursor: url absolute
+        $cssin = '.one {cursor: http://local.host/mycursor.png;}';
+        $cssout = '.one{cursor:http://local.host/mycursor.png;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+
+    public function test_vertical_align() {
+        $optimiser = $this->get_optimiser();
+
+        // Valid vertical aligns
+        $cssin = '.one {vertical-align: baseline;}';
+        $cssout = '.one{vertical-align:baseline;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {vertical-align: middle;}';
+        $cssout = '.one{vertical-align:middle;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {vertical-align: 0.75em;}';
+        $cssout = '.one{vertical-align:0.75em;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {vertical-align: 50%;}';
+        $cssout = '.one{vertical-align:50%;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        // Invalid but tollerated
+        $cssin = '.one {vertical-align: center;}';
+        $cssout = '.one{vertical-align:center;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+
+    public function test_float() {
+        $optimiser = $this->get_optimiser();
+
+        // Valid vertical aligns
+        $cssin = '.one {float: inherit;}';
+        $cssout = '.one{float:inherit;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {float: left;}';
+        $cssout = '.one{float:left;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {float: right;}';
+        $cssout = '.one{float:right;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+        $cssin = '.one {float: none;}';
+        $cssout = '.one{float:none;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        // Invalid but tollerated
+        $cssin = '.one {float: center;}';
+        $cssout = '.one{float:center;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
     }
 
@@ -400,7 +609,8 @@ class css_optimiser_testcase extends advanced_testcase {
      *
      * @param css_optimiser $optimiser
      */
-    protected function try_invalid_css_handling(css_optimiser $optimiser) {
+    protected function try_invalid_css_handling() {
+        $optimiser = $this->get_optimiser();
 
         $cssin = array(
             '.one{}',
@@ -428,7 +638,7 @@ class css_optimiser_testcase extends advanced_testcase {
             '.one {background-color:red;} .one {:blue;}',
             '.one {background-color:red;} .one {:#00F}',
         );
-        $cssout = '.one{background:#F00;}';
+        $cssout = '.one{background-color:#F00;}';
         foreach ($cssin as $css) {
             $this->assertEquals($cssout, $optimiser->process($css));
         }
@@ -450,15 +660,15 @@ class css_optimiser_testcase extends advanced_testcase {
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin = '.one {background-color:red;color;border-color:blue}';
-        $cssout = '.one{background:#F00;border-color:#00F;}';
+        $cssout = '.one{background-color:#F00;border-color:#00F;}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin  = '{background-color:#123456;color:red;}{color:green;}';
-        $cssout = "{color:#008000;background:#123456;}";
+        $cssout = "{background-color:#123456;color:#008000;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
 
         $cssin  = '.one {color:red;} {color:green;} .one {background-color:blue;}';
-        $cssout = ".one{color:#F00;background:#00F;} {color:#008000;}";
+        $cssout = ".one{color:#F00;background-color:#00F;} {color:#008000;}";
         $this->assertEquals($cssout, $optimiser->process($cssin));
     }
 
@@ -466,7 +676,9 @@ class css_optimiser_testcase extends advanced_testcase {
      * Try to break some things
      * @param css_optimiser $optimiser
      */
-    protected function try_break_things(css_optimiser $optimiser) {
+    protected function try_break_things() {
+        $optimiser = $this->get_optimiser();
+
         // Wildcard test
         $cssin  = '* {color: black;}';
         $cssout = '*{color:#000;}';
@@ -528,7 +740,7 @@ class css_optimiser_testcase extends advanced_testcase {
      * A bulk processing test
      * @param css_optimiser $optimiser
      */
-    protected function try_bulk_processing(css_optimiser $optimiser) {
+    protected function try_bulk_processing() {
         global $CFG;
         $cssin = <<<CSS
 .test .one {
@@ -567,25 +779,23 @@ class css_optimiser_testcase extends advanced_testcase {
 CSS;
 
         $cssout = <<<CSS
-.test .one{color:#F00;margin:10px;border-width:0;background:#123;}
+.test .one{margin:10px;border-width:0;color:#F00;background-color:#123;}
 .test.one{margin:15px;border:1px solid #008000;}
-#test .one{color:#000;margin:20px;}
+#test .one{margin:20px;color:#000;}
 #test #one{margin:25px;}
 .test #one{margin:30px;}
 #new.style{color:#000;}
 
-
 @media print {
-  #test .one{color:#123456;margin:40px;}
+  #test .one{margin:40px;color:#123456;}
   #test #one{margin:45px;}
 }
-
 @media print,screen {
   #test .one{color:#654321;}
 }
 CSS;
         $CFG->cssoptimiserpretty = 1;
-        $this->assertEquals($optimiser->process($cssin), $cssout);
+        $this->assertEquals($this->get_optimiser()->process($cssin), $cssout);
     }
 
     /**
@@ -663,11 +873,15 @@ CSS;
         $this->assertTrue(css_is_width('199px'));
         $this->assertTrue(css_is_width('199em'));
         $this->assertTrue(css_is_width('199%'));
-        $this->assertTrue(css_is_width('-1'));
         $this->assertTrue(css_is_width('-1px'));
         $this->assertTrue(css_is_width('auto'));
         $this->assertTrue(css_is_width('inherit'));
 
+        // Valid widths but missing their unit specifier
+        $this->assertFalse(css_is_width('0.75'));
+        $this->assertFalse(css_is_width('3'));
+        $this->assertFalse(css_is_width('-1'));
+        // Totally invalid widths
         $this->assertFalse(css_is_width('-'));
         $this->assertFalse(css_is_width('bananas'));
         $this->assertFalse(css_is_width(''));
@@ -681,7 +895,9 @@ CSS;
      *
      * @param css_optimiser $optimiser
      */
-    public function try_broken_css_found_in_moodle(css_optimiser $optimiser) {
+    public function try_broken_css_found_in_moodle() {
+        $optimiser = $this->get_optimiser();
+
         // Notice how things are out of order here but that they get corrected
         $cssin = '.test {background:url([[pix:theme|pageheaderbgred]]) top center no-repeat}';
         $cssout = '.test{background:url([[pix:theme|pageheaderbgred]]) no-repeat top center;}';
@@ -718,4 +934,144 @@ CSS;
         $cssout = '.test{opacity:0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);}';
         $this->assertEquals($cssout, $optimiser->process($cssin));
     }
+
+    /**
+     * Test keyframe declarations
+     * @param css_optimiser $optimiser
+     */
+    public function try_keyframe_css_animation() {
+        $optimiser = $this->get_optimiser();
+
+        $css = '.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url(\'[[pix:theme|fp/dnd_arrow]]\') no-repeat center;margin-left:-28px;}';
+        $this->assertEquals($css, $optimiser->process($css));
+
+        $css = '@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}';
+        $this->assertEquals($css, $optimiser->process($css));
+
+        $css  = "@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}\n";
+        $css .= "@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}\n";
+        $css .= "@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}";
+        $this->assertEquals($css, $optimiser->process($css));
+
+
+        $cssin = <<<CSS
+.test {color:#FFF;}
+.testtwo {color:#FFF;}
+@media print {
+    .test {background-color:#FFF;}
 }
+.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;margin-left:-28px;}
+@media print {
+    .test {background-color:#000;}
+}
+@keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
+@-moz-keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
+@-webkit-keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
+@media print {
+    .test {background-color:#333;}
+}
+.test {color:#888;}
+.testtwo {color:#888;}
+CSS;
+
+        $cssout = <<<CSS
+.test,
+.testtwo{color:#888;}
+.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;margin-left:-28px;}
+
+@media print {
+  .test{background-color:#333;}
+}
+@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+CSS;
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+
+
+        $cssin = <<<CSS
+.dndupload-target {display:none;}
+.dndsupported .dndupload-ready .dndupload-target {display:block;}
+.dndupload-uploadinprogress {display:none;text-align:center;}
+.dndupload-uploading .dndupload-uploadinprogress {display:block;}
+.dndupload-arrow {background:url('[[pix:theme|fp/dnd_arrow]]') center no-repeat;width:56px;height:47px;position:absolute;margin-left: -28px;/*right:46%;left:46%;*/animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;}
+@keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}@-moz-keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}@-webkit-keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
+
+/*
+ * Select Dialogue (File Manager only)
+ */
+.filemanager.fp-select .fp-select-loading {display:none;}
+.filemanager.fp-select.loading .fp-select-loading {display:block;}
+.filemanager.fp-select.loading form {display:none;}
+CSS;
+
+        $cssout = <<<CSS
+.dndupload-target{display:none;}
+.dndsupported .dndupload-ready .dndupload-target{display:block;}
+.dndupload-uploadinprogress{display:none;text-align:center;}
+.dndupload-uploading .dndupload-uploadinprogress{display:block;}
+.dndupload-arrow{background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;width:56px;height:47px;position:absolute;margin-left:-28px;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;}
+.filemanager.fp-select .fp-select-loading{display:none;}
+.filemanager.fp-select.loading .fp-select-loading{display:block;}
+.filemanager.fp-select.loading form{display:none;}
+
+@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
+CSS;
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+
+    /**
+     * Test media declarations
+     * @param css_optimiser $optimiser
+     */
+    public function try_media_rules() {
+        $optimiser = $this->get_optimiser();
+
+        $cssin = "@media print {\n  .test{background-color:#333;}\n}";
+        $cssout = "@media print {\n  .test{background-color:#333;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media screen and (min-width:30px) {\n  #region-main-box{left: 30px;float: left;}\n}";
+        $cssout = "@media screen and (min-width:30px) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media all and (min-width:500px) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $cssout = "@media all and (min-width:500px) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media (min-width:500px) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $cssout = "@media (min-width:500px) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media screen and (color), projection and (color) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $cssout = "@media screen and (color),projection and (color) {\n  #region-main-box{left:30px;float:left;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media print {\n  .test{background-color:#000;}\n}@media print {\n  .test{background-color:#FFF;}\n}";
+        $cssout = "@media print {\n  .test{background-color:#FFF;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media screen and (min-width:30px) {\n  #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:30px) {\n  #region-main-box{background-color:#FFF;}\n}";
+        $cssout = "@media screen and (min-width:30px) {\n  #region-main-box{background-color:#FFF;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+
+        $cssin = "@media screen and (min-width:30px) {\n  #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:31px) {\n  #region-main-box{background-color:#FFF;}\n}";
+        $cssout = "@media screen and (min-width:30px) {\n  #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:31px) {\n  #region-main-box{background-color:#FFF;}\n}";
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+
+
+    public function test_css_optimisation_ordering() {
+        $optimiser = $this->get_optimiser();
+
+        $css = '.test{display:none;} .dialogue{display:block;} .dialogue-hidden{display:none;}';
+        $this->assertEquals($css, $optimiser->process($css));
+
+        $cssin = '.test{display:none;} .dialogue-hidden{display:none;} .dialogue{display:block;}';
+        $cssout = '.test, .dialogue-hidden{display:none;} .dialogue{display:block;}';
+        $this->assertEquals($cssout, $optimiser->process($cssin));
+    }
+}
\ No newline at end of file
index f68f3d5..84362fe 100644 (file)
@@ -1090,6 +1090,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
 
     if ($options['filter']) {
         $filtermanager = filter_manager::instance();
+        $filtermanager->setup_page_for_filters($PAGE, $context); // Setup global stuff filters may have.
     } else {
         $filtermanager = new null_filter_manager();
     }
@@ -1301,7 +1302,9 @@ function format_string($string, $striplinks = true, $options = NULL) {
     $string = replace_ampersands_not_followed_by_entity($string);
 
     if (!empty($CFG->filterall)) {
-        $string = filter_manager::instance()->filter_string($string, $options['context']);
+        $filtermanager = filter_manager::instance();
+        $filtermanager->setup_page_for_filters($PAGE, $options['context']); // Setup global stuff filters may have.
+        $string = $filtermanager->filter_string($string, $options['context']);
     }
 
     // If the site requires it, strip ALL tags from this string
index d4c71fd..a611f5f 100644 (file)
 $messageproviders = array (
 
     // Ordinary assignment submissions
-    'assign_student_notification' => array(
-    ),
-    'assign_grader_notification' => array(
-        'capability' => 'mod/assign:grade'
+    'assign_notification' => array(
     )
 
 );
diff --git a/mod/assign/gradingactionsform.php b/mod/assign/gradingactionsform.php
deleted file mode 100644 (file)
index 46eeda1..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * This file contains the forms to create and edit an instance of this module
- *
- * @package   mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
-
-
-/** Include formslib.php */
-require_once ($CFG->libdir.'/formslib.php');
-/** Include locallib.php */
-require_once($CFG->dirroot . '/mod/assign/locallib.php');
-
-/**
- * Assignment grading actions form
- *
- * @package   mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class mod_assign_grading_actions_form extends moodleform {
-    /**
-     * The definition for this form (called by the parent constructor)
-     */
-    function definition() {
-        $mform = $this->_form;
-        $data = $this->_customdata;
-        $links = $data['links'];
-        $cm = $data['cm'];
-        $mform->addElement('header', 'general', get_string('gradingactions', 'assign'));
-        // visible elements
-        $autosubmit = array('onchange'=>'form.submit();');
-        $mform->addElement('select', 'url', '', $links, $autosubmit);
-
-        // hidden params
-        $mform->addElement('hidden', 'id', $cm);
-        $mform->setType('id', PARAM_INT);
-        $mform->addElement('hidden', 'action', 'redirect');
-        $mform->setType('action', PARAM_ALPHA);
-
-        // buttons
-        $this->add_action_buttons(false, get_string('submitaction', 'assign'));
-    }
-}
index 626a7cc..2ae00a2 100644 (file)
@@ -45,7 +45,6 @@ class mod_assign_grading_batch_operations_form extends moodleform {
         $mform = $this->_form;
         $instance = $this->_customdata;
 
-        $mform->addElement('header', 'general', get_string('batchoperations', 'assign'));
         // visible elements
         $options = array();
         $options['lock'] = get_string('locksubmissions', 'assign');
@@ -53,14 +52,16 @@ class mod_assign_grading_batch_operations_form extends moodleform {
         if ($instance['submissiondrafts']) {
             $options['reverttodraft'] = get_string('reverttodraft', 'assign');
         }
-        $mform->addElement('select', 'operation', get_string('batchoperationsdescription', 'assign'), $options, array('class'=>'operation ignoredirty'));
-        $mform->addHelpButton('operation', 'batchoperationsdescription', 'assign');
         $mform->addElement('hidden', 'action', 'batchgradingoperation');
         $mform->addElement('hidden', 'id', $instance['cm']);
         $mform->addElement('hidden', 'selectedusers', '', array('class'=>'selectedusers'));
         $mform->addElement('hidden', 'returnaction', 'grading');
 
-        $mform->addElement('submit', 'submit', get_string('submit'));
+        $objs = array();
+        $objs[] =& $mform->createElement('select', 'operation', '', $options);
+        $objs[] =& $mform->createElement('submit', 'submit', get_string('go'));
+        $mform->addElement('group', 'actionsgrp', get_string('batchoperationsdescription', 'assign'), $objs, ' ', false);
+
     }
 
 }
index 833276b..bfe387b 100644 (file)
@@ -56,9 +56,7 @@ $string['assignsubmission'] = 'Submission plugin';
 $string['assignsubmissionpluginname'] = 'Submission plugin';
 $string['availability'] = 'Availability';
 $string['backtoassignment'] = 'Back to assignment';
-$string['batchoperations'] = 'Batch operations';
-$string['batchoperationsdescription'] = 'Perform action on selected row(s)';
-$string['batchoperationsdescription_help'] = 'The selected operation will be performed on all of the selected rows in the grading table. ';
+$string['batchoperationsdescription'] = 'With selected...';
 $string['batchoperationconfirmlock'] = 'Lock all selected submissions?';
 $string['batchoperationconfirmunlock'] = 'Unlock all selected submissions?';
 $string['batchoperationconfirmreverttodraft'] = 'Revert selected submissions to draft?';
@@ -135,8 +133,7 @@ $string['gradeoutofhelp'] = 'Grade';
 $string['gradeoutofhelp_help'] = 'Enter the grade for the student\'s submission here. You may include decimals.';
 $string['gradestudent'] = 'Grade student: (id={$a->id}, fullname={$a->fullname}). ';
 $string['grading'] = 'Grading';
-$string['gradingoptions'] = 'Grade listing options';
-$string['gradingactions'] = 'Grading actions';
+$string['gradingoptions'] = 'Options';
 $string['gradingstatus'] = 'Grading status';
 $string['gradingstudentprogress'] = 'Grading student {$a->index} of {$a->count}';
 $string['gradingsummary'] = 'Grading summary';
@@ -150,8 +147,7 @@ $string['locksubmissionforstudent'] = 'Prevent any more submissions for student:
 $string['locksubmissions'] = 'Lock submissions';
 $string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins';
 $string['manageassignsubmissionplugins'] = 'Manage assignment submission plugins';
-$string['messageprovider:assign_student_notification'] = 'Assignment student notifications';
-$string['messageprovider:assign_grader_notification'] = 'Assignment grader notifications';
+$string['messageprovider:assign_notification'] = 'Assignment notifications';
 $string['modulename'] = 'Assignment';
 $string['modulename_help'] = 'The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback.
 
@@ -196,7 +192,7 @@ $string['reverttodraft'] = 'Revert the submission to draft status.';
 $string['reverttodraftshort'] = 'Revert the submission to draft';
 $string['reviewed'] = 'Reviewed';
 $string['savechanges'] = 'Save changes';
-$string['saveallchanges'] = 'Save all changes';
+$string['saveallquickgradingchanges'] = 'Save all quick grading changes';
 $string['savenext'] = 'Save and show next';
 $string['sendnotifications'] = 'Notify graders about submissions';
 $string['sendnotifications_help'] = 'If enabled, graders (usually teachers) receive a message whenever a student submits an assignment, early, on time and late. Message methods are configurable.';
index b8d600a..a7466d1 100644 (file)
@@ -361,8 +361,6 @@ class assign {
         } else if ($action == 'nextgrade') {
             $mform = null;
             $o .= $this->view_single_grade_page($mform, 1);
-        } else if ($action == 'redirect') {
-            redirect(required_param('url', PARAM_TEXT));
         } else if ($action == 'grade') {
             $o .= $this->view_single_grade_page($mform);
         } else if ($action == 'viewpluginassignfeedback') {
@@ -904,10 +902,12 @@ class assign {
                 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
                 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
                 return $o;
-            } else if ($grade == -1 || $grade === null) {
-                return '-';
             } else {
-                return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
+                if ($grade == -1 || $grade === null) {
+                    return '-';
+                } else {
+                    return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
+                }
             }
 
         } else {
@@ -1181,7 +1181,7 @@ class assign {
 
             // need to send this to the student
             $messagetype = 'feedbackavailable';
-            $eventtype = 'assign_student_notification';
+            $eventtype = 'assign_notification';
             $updatetime = $submission->lastmodified;
             $modulename = get_string('modulename', 'assign');
             self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $mod, $contextmodule, $course, $modulename, $submission->name);
@@ -1690,32 +1690,22 @@ class assign {
         global $USER, $CFG;
         // Include grading options form
         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
-        require_once($CFG->dirroot . '/mod/assign/gradingactionsform.php');
         require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
         $o = '';
 
         $links = array();
-        $selecturl = (string)(new moodle_url('/mod/assign/view.php',
-                                             array('action'=>'grading', 'id'=>$this->get_course_module()->id)));
-        $links[$selecturl] = get_string('selectlink', 'assign');
         if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
                 has_capability('moodle/grade:viewall', $this->get_course_context())) {
-            $gradebookurl = (string) (new moodle_url('/grade/report/grader/index.php',
-                                                     array('id' => $this->get_course()->id)));
+            $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
             $links[$gradebookurl] = get_string('viewgradebook', 'assign');
         }
         if ($this->is_any_submission_plugin_enabled()) {
-            $downloadurl = (string) (new moodle_url('/mod/assign/view.php',
-                                                    array('id' => $this->get_course_module()->id,
-                                                          'action' => 'downloadall')));
+            $downloadurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=downloadall';
             $links[$downloadurl] = get_string('downloadall', 'assign');
         }
-        $gradingactionsform = new mod_assign_grading_actions_form(null,
-                                                                  array('links'=>$links,
-                                                                        'cm'=>$this->get_course_module()->id),
-                                                                  'post', '',
-                                                                  array('class'=>'gradingactionsform'));
+
+        $gradingactions = new url_select($links);
 
         $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
 
@@ -1757,8 +1747,11 @@ class assign {
             plagiarism_update_status($this->get_course(), $this->get_course_module());
         }
 
-        $o .= $this->output->render(new assign_form('gradingactionsform', $gradingactionsform));
-        $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
+        $actionformtext = $this->output->render($gradingactions);
+        $o .= $this->output->render(new assign_header($this->get_instance(),
+                                                      $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
+        $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
+
 
         // load and print the table of submissions
         if ($showquickgrading && $quickgrading) {
@@ -1777,6 +1770,7 @@ class assign {
             // if no enrolled user in a course then don't display the batch operations feature
             $o .= $this->output->render(new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform));
         }
+        $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
         return $o;
     }
 
@@ -1795,11 +1789,6 @@ class assign {
 
         // only load this if it is
 
-        $o .= $this->output->render(new assign_header($this->get_instance(),
-                                                      $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign')));
-        $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
-
-
         $o .= $this->view_grading_table();
 
         $o .= $this->view_footer();
@@ -2449,7 +2438,7 @@ class assign {
             return;
         }
         $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
-        $this->send_notification($user, $user, 'submissionreceipt', 'assign_student_notification', $submission->timemodified);
+        $this->send_notification($user, $user, 'submissionreceipt', 'assign_notification', $submission->timemodified);
     }
 
     /**
@@ -2471,7 +2460,7 @@ class assign {
         $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
         if ($teachers = $this->get_graders($user->id)) {
             foreach ($teachers as $teacher) {
-                $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_grader_notification', $submission->timemodified);
+                $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_notification', $submission->timemodified);
             }
         }
     }
index 0598b57..9382582 100644 (file)
@@ -55,8 +55,7 @@ class mod_assign_quick_grading_form extends moodleform {
         $mform->setType('action', PARAM_ALPHA);
 
         // buttons
-        $mform->addElement('header', 'general', get_string('quickgrading', 'assign'));
-        $mform->addElement('submit', 'savequickgrades', get_string('saveallchanges', 'assign'));
+        $mform->addElement('submit', 'savequickgrades', get_string('saveallquickgradingchanges', 'assign'));
     }
 }
 
index 2f52c34..cb1daf9 100644 (file)
@@ -365,6 +365,8 @@ class assign_header implements renderable {
     var $coursemoduleid = 0;
     /** @var string $subpage optional subpage (extra level in the breadcrumbs) */
     var $subpage = '';
+    /** @var string $preface optional preface (text to show before the heading) */
+    var $preface = '';
 
     /**
      * Constructor
@@ -374,13 +376,15 @@ class assign_header implements renderable {
      * @param bool $showintro  - show or hide the intro
      * @param int $coursemoduleid  - the course module id
      * @param string $subpage  - an optional sub page in the navigation
+     * @param string $preface  - an optional preface to show before the heading
      */
-    public function __construct(stdClass $assign, $context, $showintro, $coursemoduleid, $subpage='') {
+    public function __construct(stdClass $assign, $context, $showintro, $coursemoduleid, $subpage='', $preface='') {
         $this->assign = $assign;
         $this->context = $context;
         $this->showintro = $showintro;
         $this->coursemoduleid = $coursemoduleid;
         $this->subpage = $subpage;
+        $this->preface = $preface;
     }
 }
 
index 9f57157..a55c621 100644 (file)
@@ -206,6 +206,9 @@ class mod_assign_renderer extends plugin_renderer_base {
         $this->page->set_heading($header->assign->name);
 
         $o .= $this->output->header();
+        if ($header->preface) {
+            $o .= $header->preface;
+        }
         $o .= $this->output->heading(format_string($header->assign->name,false, array('context' => $header->context)));
 
         if ($header->showintro) {
index 11b8aa6..840ad17 100644 (file)
@@ -24,7 +24,6 @@ div.gradingsummary .generaltable {
 .gradingsummarytable,
 .feedbacktable,
 .lockedsubmission,
-.gradingbatchoperationsform,
 .submissionsummarytable {
     margin-top: 1em;
 }
@@ -39,9 +38,10 @@ div.submissionsummarytable table tbody tr td.c0 {
     font-weight: 900;
 }
 
-.jsenabled .gradingactionsform .hidden {display: none;}
-.jsenabled .gradingoptionsform .hidden {display: none;}
+.jsenabled .gradingoptionsform .fsubmit {display: none;}
 .jsenabled .gradingtable .c1 select {display: none;}
+.quickgradingform .mform fieldset { margin: 0px; padding: 0px; }
+.gradingbatchoperationsform .mform fieldset { margin: 0px; padding: 0px; }
 
 td.submissionstatus,
 div.submissionstatus,
@@ -127,4 +127,4 @@ div.earlysubmission {
 
 #page-mod-assign-view div.gradingtable tr .quickgrademodified {
     background-color: #FFCC99;
-}
\ No newline at end of file
+}
index eebfe57..3852d07 100644 (file)
@@ -221,8 +221,8 @@ class assign_upgrade_manager {
             $newassignment->update_calendar($newcoursemodule->id);
 
             // copy the grades from the old assignment to the new one
-            $DB->set_field('grade_items', 'itemmodule', 'assign', array('iteminstance'=>$oldassignment->id));
-            $DB->set_field('grade_items', 'iteminstance', $newassignment->get_instance()->id, array('iteminstance'=>$oldassignment->id));
+            $DB->set_field('grade_items', 'itemmodule', 'assign', array('iteminstance'=>$oldassignment->id, 'itemmodule'=>'assignment'));
+            $DB->set_field('grade_items', 'iteminstance', $newassignment->get_instance()->id, array('iteminstance'=>$oldassignment->id, 'itemmodule'=>'assign'));
             $gradesdone = true;
 
         } catch (Exception $exception) {
@@ -234,8 +234,8 @@ class assign_upgrade_manager {
             // roll back the grades changes
             if ($gradesdone) {
                 // copy the grades from the old assignment to the new one
-                $DB->set_field('grade_items', 'itemmodule', 'assignment', array('iteminstance'=>$newassignment->get_instance()->id));
-                $DB->set_field('grade_items', 'iteminstance', $oldassignment->id, array('iteminstance'=>$newassignment->get_instance()->id));
+                $DB->set_field('grade_items', 'itemmodule', 'assignment', array('iteminstance'=>$newassignment->get_instance()->id, 'itemmodule'=>'assign'));
+                $DB->set_field('grade_items', 'iteminstance', $oldassignment->id, array('iteminstance'=>$newassignment->get_instance()->id, 'itemmodule'=>'assignment'));
             }
             // roll back the completion changes
             if ($completiondone) {
index af599d0..7adab47 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics)
-$module->version  = 2012061100;    // The current module version (Date: YYYYMMDDXX)
+$module->version  = 2012061400;    // The current module version (Date: YYYYMMDDXX)
 $module->requires = 2012050300;    // Requires this Moodle version
 $module->cron     = 60;
 
index 40c7ccf..a03d0ea 100644 (file)
@@ -481,16 +481,10 @@ abstract class question_testcase extends advanced_testcase {
             $compare = (array)$compare;
             foreach ($expect as $k=>$v) {
                 if (!array_key_exists($k, $compare)) {
-                    if (!$message) {
-                        $message = "Property $k does not exist";
-                    }
-                    $this->fail($message);
+                    $this->fail("Property $k does not exist");
                 }
                 if ($v != $compare[$k]) {
-                    if (!$message) {
-                        $message = "Property $k is different";
-                    }
-                    $this->fail($message);
+                    $this->fail("Property $k is different");
                 }
             }
             $this->assertTrue(true);
index 713b247..52e42cc 100644 (file)
@@ -1247,8 +1247,10 @@ class question_possible_response {
      * {@link question_type::get_possible_responses()}.
      */
     public $responseclass;
-    /** @var string the actual response the student gave to this part. */
+
+    /** @var string the (partial) credit awarded for this responses. */
     public $fraction;
+
     /**
      * Constructor, just an easy way to set the fields.
      * @param string $responseclassid see the field descriptions above.
index b64d36a..6578afe 100644 (file)
@@ -308,6 +308,23 @@ class repository_boxnet extends repository {
         }
     }
 
+    /**
+     * Return the source information
+     *
+     * @param stdClass $url
+     * @return string|null
+     */
+    public function get_file_source_info($url) {
+        $array = explode('/', $url);
+        $fileid = array_pop($array);
+        $fileinfo = $this->boxclient->get_file_info($fileid);
+        if (!empty($fileinfo)) {
+            return 'Box:' . (string)$fileinfo->file_name;
+        } else {
+            return $url;
+        }
+    }
+
     /**
      * Repository method to serve the referenced file
      *
index dccf84f..7a12bb9 100644 (file)
@@ -425,6 +425,16 @@ class repository_dropbox extends repository {
         }
     }
 
+    /**
+     * Return the source information
+     *
+     * @param stdClass $filepath
+     * @return string|null
+     */
+    public function get_file_source_info($filepath) {
+        return 'Dropbox:' . $filepath;
+    }
+
     /**
      * Repository method to serve the referenced file
      *
index 5282715..b295113 100644 (file)
@@ -152,6 +152,16 @@ class repository_filesystem extends repository {
         return array('path'=>$file, 'url'=>'');
     }
 
+    /**
+     * Return the source information
+     *
+     * @param stdClass $filepath
+     * @return string|null
+     */
+    public function get_file_source_info($filepath) {
+        return $filepath;
+    }
+
     public function logout() {
         return true;
     }
index b54ad93..6886638 100644 (file)
@@ -222,9 +222,13 @@ class repository_flickr extends repository {
         return $this->search('', $page);
     }
 
-    public function get_link($photo_id) {
-        global $CFG;
-        $result = $this->flickr->photos_getSizes($photo_id);
+    /**
+     * Return photo url by given photo id
+     * @param string $photoid
+     * @return string
+     */
+    private function build_photo_url($photoid) {
+        $result = $this->flickr->photos_getSizes($photoid);
         $url = '';
         if(!empty($result[4])) {
             $url = $result[4]['source'];
@@ -236,23 +240,18 @@ class repository_flickr extends repository {
         return $url;
     }
 
+    public function get_link($photoid) {
+        return $this->build_photo_url($photoid);
+    }
+
     /**
      *
-     * @param string $photo_id
+     * @param string $photoid
      * @param string $file
      * @return string
      */
-    public function get_file($photo_id, $file = '') {
-        global $CFG;
-        $result = $this->flickr->photos_getSizes($photo_id);
-        $url = '';
-        if(!empty($result[4])) {
-            $url = $result[4]['source'];
-        } elseif(!empty($result[3])) {
-            $url = $result[3]['source'];
-        } elseif(!empty($result[2])) {
-            $url = $result[2]['source'];
-        }
+    public function get_file($photoid, $file = '') {
+        $url = $this->build_photo_url($photoid);
         $path = $this->prepare_file($file);
         $fp = fopen($path, 'w');
         $c = new curl;
@@ -315,4 +314,14 @@ class repository_flickr extends repository {
     public function supported_returntypes() {
         return (FILE_INTERNAL | FILE_EXTERNAL);
     }
+
+    /**
+     * Return the source information
+     *
+     * @param string $photoid
+     * @return string|null
+     */
+    public function get_file_source_info($photoid) {
+        return $this->build_photo_url($photoid);
+    }
 }
index d422483..083e4c7 100644 (file)
@@ -396,9 +396,13 @@ class repository_flickr_public extends repository {
         return $str;
     }
 
-    public function get_link($photo_id) {
-        global $CFG;
-        $result = $this->flickr->photos_getSizes($photo_id);
+    /**
+     * Return photo url by given photo id
+     * @param string $photoid
+     * @return string
+     */
+    private function build_photo_url($photoid) {
+        $result = $this->flickr->photos_getSizes($photoid);
         $url = '';
         if(!empty($result[4])) {
             $url = $result[4]['source'];
@@ -410,23 +414,27 @@ class repository_flickr_public extends repository {
         return $url;
     }
 
+    public function get_link($photoid) {
+        return $this->build_photo_id($photoid);
+    }
+
     /**
      *
      * @global object $CFG
-     * @param string $photo_id
+     * @param string $photoid
      * @param string $file
      * @return string
      */
-    public function get_file($photo_id, $file = '') {
+    public function get_file($photoid, $file = '') {
         global $CFG;
-        $info = $this->flickr->photos_getInfo($photo_id);
+        $info = $this->flickr->photos_getInfo($photoid);
         if ($info['owner']['realname']) {
             $author = $info['owner']['realname'];
         } else {
             $author = $info['owner']['username'];
         }
         $copyright = get_string('author', 'repository') . ': ' . $author;
-        $result = $this->flickr->photos_getSizes($photo_id);
+        $result = $this->flickr->photos_getSizes($photoid);
         // download link
         $source = '';
         // flickr photo page
@@ -517,4 +525,14 @@ class repository_flickr_public extends repository {
     public function supported_returntypes() {
         return (FILE_INTERNAL | FILE_EXTERNAL);
     }
+
+    /**
+     * Return the source information
+     *
+     * @param string $photoid photo id
+     * @return string|null
+     */
+    public function get_file_source_info($photoid) {
+        return $this->build_photo_url($photoid);
+    }
 }
index ce97d71..db4e45b 100644 (file)
@@ -1226,6 +1226,16 @@ abstract class repository {
         return null;
     }
 
+    /**
+     * Return the source information
+     *
+     * @param stdClass $url
+     * @return string|null
+     */
+    public function get_file_source_info($url) {
+        return $url;
+    }
+
     /**
      * Move file from download folder to file pool using FILE API
      *
@@ -2308,6 +2318,25 @@ abstract class repository {
         $synchronized[$file->get_id()] = true;
         return true;
     }
+
+    /**
+     * Build draft file's source field
+     *
+     * {@link file_restore_source_field_from_draft_file()}
+     * XXX: This is a hack for file manager (MDL-28666)
+     * For newly created  draft files we have to construct
+     * source filed in php serialized data format.
+     * File manager needs to know the original file information before copying
+     * to draft area, so we append these information in mdl_files.source field
+     *
+     * @param string $source
+     * @return string serialised source field
+     */
+    public static function build_source_field($source) {
+        $sourcefield = new stdClass;
+        $sourcefield->source = $source;
+        return serialize($sourcefield);
+    }
 }
 
 /**
index 3c06412..45f0662 100644 (file)
@@ -265,6 +265,11 @@ switch ($action) {
                     $event['existingfile']->filename = $saveas_filename;
                     $event['existingfile']->url      = moodle_url::make_draftfile_url($itemid, $saveas_path, $saveas_filename)->out();;
                 } else {
+
+                    // {@link repository::build_source_field()}
+                    $sourcefield = $repo->get_file_source_info($source);
+                    $record->source = $repo::build_source_field($sourcefield);
+
                     $storedfile = $fs->create_file_from_reference($record, $repo_id, $reference);
                     $event = array(
                         'url'=>moodle_url::make_draftfile_url($storedfile->get_itemid(), $storedfile->get_filepath(), $storedfile->get_filename())->out(),
@@ -302,14 +307,9 @@ switch ($action) {
                     throw new file_exception('maxbytes');
                 }
 
-                // {@link file_restore_source_field_from_draft_file()}
-                $sourcefield = '';
-                if (!empty($downloadedfile['url'])) {
-                    $source = new stdClass;
-                    $source->source = $downloadedfile['url'];
-                    $sourcefield = serialize($source);
-                }
-                $record->source = $sourcefield;
+                // {@link repository::build_source_field()}
+                $sourcefield = $repo->get_file_source_info($source);
+                $record->source = $repo::build_source_field($sourcefield);
 
                 $info = repository::move_to_filepool($downloadedfile['path'], $record);
                 if (empty($info)) {
index 2479050..5558620 100644 (file)
@@ -119,6 +119,16 @@ class repository_s3 extends repository {
         return array('path'=>$path);
     }
 
+    /**
+     * Return the source information
+     *
+     * @param stdClass $filepath
+     * @return string
+     */
+    public function get_file_source_info($filepath) {
+        return 'Amazon S3:' . $filepath;
+    }
+
     /**
      * S3 doesn't require login
      *
index a307ab6..cf023dd 100644 (file)
@@ -143,6 +143,9 @@ class repository_upload extends repository {
         self::antivir_scan_file($_FILES[$elname]['tmp_name'], $_FILES[$elname]['name'], true);
         @chmod($_FILES[$elname]['tmp_name'], $permissions);
 
+        // {@link repository::build_source_field()}
+        $record->source = self::build_source_field($_FILES[$elname]['name']);
+
         if (empty($saveas_filename)) {
             $record->filename = clean_param($_FILES[$elname]['name'], PARAM_FILE);
         } else {
@@ -186,7 +189,6 @@ class repository_upload extends repository {
         }
         $record->contextid = $context->id;
         $record->userid    = $USER->id;
-        $record->source    = '';
 
         if (repository::draftfile_exists($record->itemid, $record->filepath, $record->filename)) {
             $existingfilename = $record->filename;
index 16f2eb0..20725c8 100644 (file)
@@ -217,5 +217,15 @@ EOD;
     public function supported_returntypes() {
         return (FILE_INTERNAL | FILE_EXTERNAL);
     }
+
+    /**
+     * Return the source information
+     *
+     * @param stdClass $url
+     * @return string|null
+     */
+    public function get_file_source_info($url) {
+        return $url;
+    }
 }
 
index 50d8556..cc4bf2b 100644 (file)
@@ -127,4 +127,14 @@ EOD;
     public function supported_returntypes() {
         return (FILE_INTERNAL | FILE_EXTERNAL);
     }
+
+    /**
+     * Return the source information
+     *
+     * @param stdClass $url
+     * @return string|null
+     */
+    public function get_file_source_info($url) {
+        return $url;
+    }
 }
index 83a10a7..c92dc93 100644 (file)
@@ -156,6 +156,7 @@ if ($thisdevice == "default" || $thisdevice == "tablet" || optional_param('mymob
 // Add the required JavaScript to the page
 $THEME->javascripts = array(
     'jquery-1.6.4.min',
+    'jquery-noconflict',
     'custom',
     'jquery.mobile-1.0',
     'scrollview',
@@ -167,4 +168,4 @@ $THEME->rendererfactory = 'theme_overridden_renderer_factory';
 
 // This theme doesn't support CSS optimisation. The JQuery CSS has already been optimised in a way that
 // is not compatible with the CSS optimiser in Moodle.
-$THEME->supportscssoptimisation = false;
\ No newline at end of file
+$THEME->supportscssoptimisation = false;
diff --git a/theme/mymobile/javascript/jquery-noconflict.js b/theme/mymobile/javascript/jquery-noconflict.js
new file mode 100644 (file)
index 0000000..aa32286
--- /dev/null
@@ -0,0 +1 @@
+jQuery.noConflict();
\ No newline at end of file
index 172bc3d..5a01410 100644 (file)
@@ -36,6 +36,7 @@ if ($showswatch == "light") {
     $databodytheme = 'c';
 }
 $mypagetype = $PAGE->pagetype;
+$bodyclasses = array();
 
 echo $OUTPUT->doctype() ?>
 <html <?php echo $OUTPUT->htmlattributes() ?>>
index 65e9699..4062580 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012061400.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012061500.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.3dev (Build: 20120612)'; // Human-friendly version name
+$release  = '2.3dev (Build: 20120615)'; // Human-friendly version name
 
 $branch   = '23';                       // this version's branch
 $maturity = MATURITY_ALPHA;             // this version's maturity level