From 6c159262f016b74a474d31c613387b00f03ad589 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 20 Apr 2021 12:06:03 +0100 Subject: [PATCH] MDL-70970 quizaccess_seb: consistent property array sorting by key. In PHP8.0 using `ksort` was producing incorrect results by sorting keys differing only in case in the wrong order. This change makes sorting consistent between PHP versions. Co-Authored-By: Tim Hunt --- .../accessrule/seb/classes/property_list.php | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/mod/quiz/accessrule/seb/classes/property_list.php b/mod/quiz/accessrule/seb/classes/property_list.php index 57525e6573d..d6f47c88b81 100644 --- a/mod/quiz/accessrule/seb/classes/property_list.php +++ b/mod/quiz/accessrule/seb/classes/property_list.php @@ -34,9 +34,9 @@ use CFPropertyList\CFNumber; use CFPropertyList\CFPropertyList; use CFPropertyList\CFString; use CFPropertyList\CFType; +use \Collator; use \DateTime; -defined('MOODLE_INTERNAL') || die(); /** * Wrapper for CFPropertyList to handle low level iteration. @@ -322,6 +322,8 @@ class property_list { /** * Recursively sort array alphabetically by key. * + * @link https://safeexambrowser.org/developer/seb-config-key.html + * * @param array $array Top level array to process. * @return array Processed array. */ @@ -331,10 +333,23 @@ class property_list { $array[$key] = $this->array_sort($array[$key]); } } - // Sort assoc array. From SEB docs - "Use non-localized (culture invariant), non-ASCII value based case - // insensitive ordering." + // Sort assoc array. From SEB docs: + // + // All elements from the plist XML must be ordered (alphabetically sorted) by their key names. Use + // a recursive method to apply ordering also to nested dictionaries contained in the root-level dictionary + // and in arrays. Use non-localized (culture invariant), non-ASCII value based case insensitive ordering. + // For example the key allowWlan comes before allowWLAN. Cocoa/Obj-C and .NET/C# + // usually use this case insensitive ordering as default, but PHP for example doesn't. if ($this->is_associative_array($array)) { - ksort($array, SORT_STRING | SORT_FLAG_CASE); + // Note this is a pragmatic solution as none of the native PHP *sort method appear to sort strings that + // differ only in case (e.g. ["allowWLAN", "allowWlan"] is expected to have the lower version first). + $keys = array_keys($array); + (new Collator('root'))->asort($keys); // Use Unicode Collation Algorithm (UCA). + $original = $array; + $array = []; + foreach ($keys as $key) { + $array[$key] = $original[$key]; + } } return $array;