Index: branches/salvatoreingala/Gadgets/Gadgets_body.php |
— | — | @@ -160,19 +160,84 @@ |
161 | 161 | * @param &$options |
162 | 162 | */ |
163 | 163 | public static function userLoadOptions( $user, &$options ) { |
| 164 | + //Only if it's current user |
| 165 | + $curUser = RequestContext::getMain()->getUser(); |
| 166 | + if ( $curUser->getID() !== $user->getID() ) { |
| 167 | + return true; |
| 168 | + } |
| 169 | + |
164 | 170 | //Remove gadget-*-config options, since they must not be delivered |
165 | 171 | //via mw.user.options like other user preferences |
166 | | - foreach ( $options as $option => $value ){ |
167 | | - //TODO: Regexp not coherent with current gadget's naming rules |
168 | | - if ( preg_match( '/gadget-[a-zA-Z][a-zA-Z0-9_]*-config/', $option ) ) { |
169 | | - //TODO: cache them before unsetting |
| 172 | + $gadgets = Gadget::loadList(); |
| 173 | + |
| 174 | + if ( !$gadgets ) { |
| 175 | + return true; |
| 176 | + } |
| 177 | + |
| 178 | + $existingPrefs = array(); |
| 179 | + foreach ( $options as $option => $value ) { |
| 180 | + $m = array(); |
| 181 | + if ( preg_match( '/gadget-([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)-config/', $option, $m ) ) { |
| 182 | + $gadgetName = $m[1]; |
| 183 | + $existingPrefs[$gadgetName] = $value; |
170 | 184 | unset( $options[$option] ); |
171 | 185 | } |
172 | 186 | } |
| 187 | + |
| 188 | + foreach ( $gadgets as $gadget ) { |
| 189 | + $prefsDescription = $gadget->getPrefsDescription(); |
| 190 | + |
| 191 | + if ( $prefsDescription === null ) { |
| 192 | + continue; |
| 193 | + } |
| 194 | + |
| 195 | + if ( isset( $existingPrefs[$gadget->getName()] ) ) { |
| 196 | + $userPrefs = FormatJson::decode( $existingPrefs[$gadget->getName()], true ); |
| 197 | + } |
| 198 | + |
| 199 | + if ( !isset( $userPrefs ) ) { |
| 200 | + $userPrefs = array(); //no saved prefs, use defaults |
| 201 | + } |
| 202 | + |
| 203 | + Gadget::matchPrefsWithDescription( $prefsDescription, $userPrefs ); |
| 204 | + |
| 205 | + $gadget->setPrefs( $userPrefs ); |
| 206 | + } |
| 207 | + |
173 | 208 | return true; |
174 | 209 | } |
175 | 210 | |
| 211 | + /** |
| 212 | + * UserSaveOptions hook handler. |
| 213 | + * @param $user |
| 214 | + * @param &$options |
| 215 | + */ |
| 216 | + public static function userSaveOptions( $user, &$options ) { |
| 217 | + //Only if it's current user |
| 218 | + $curUser = RequestContext::getMain()->getUser(); |
| 219 | + if ( $curUser->getID() !== $user->getID() ) { |
| 220 | + return true; |
| 221 | + } |
| 222 | + |
| 223 | + //Reinsert gadget-*-config options, so they can be saved back |
| 224 | + $gadgets = Gadget::loadList(); |
| 225 | + |
| 226 | + if ( !$gadgets ) { |
| 227 | + return true; |
| 228 | + } |
| 229 | + |
| 230 | + foreach ( $gadgets as $gadget ) { |
| 231 | + if ( $gadget->getPrefs() !== null ) { |
| 232 | + //TODO: should remove prefs that equal their default |
176 | 233 | |
| 234 | + $prefsJson = FormatJson::encode( $gadget->getPrefs() ); |
| 235 | + $options["gadget-{$gadget->getName()}-config"] = $prefsJson; |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + return true; |
| 240 | + } |
| 241 | + |
177 | 242 | /** |
178 | 243 | * Adds one legacy script to output. |
179 | 244 | * |
— | — | @@ -229,6 +294,12 @@ |
230 | 295 | $category; |
231 | 296 | |
232 | 297 | |
| 298 | + //Map from gadget names to preferences of current user. |
| 299 | + //This is needed because gadget preferences are retrieved and saved |
| 300 | + //in UserLoadOptions and UserSaveOptions hooks handlers instead than |
| 301 | + //in Gadget class constructor. |
| 302 | + private static $preferencesCache = array(); |
| 303 | + |
233 | 304 | //Syntax specifications of preference description language |
234 | 305 | private static $prefsDescriptionSpecifications = array( |
235 | 306 | 'boolean' => array( |
— | — | @@ -682,6 +753,9 @@ |
683 | 754 | wfProfileIn( __METHOD__ ); |
684 | 755 | $key = wfMemcKey( 'gadgets-definition', self::GADGET_CLASS_VERSION ); |
685 | 756 | |
| 757 | + //Force loading user options |
| 758 | + RequestContext::getMain()->getUser()->getOptions(); |
| 759 | + |
686 | 760 | if ( $forceNewText === null ) { |
687 | 761 | //cached? |
688 | 762 | $gadgets = $wgMemc->get( $key ); |
— | — | @@ -807,29 +881,32 @@ |
808 | 882 | return true; |
809 | 883 | } |
810 | 884 | |
811 | | - //Gets preferences for gadget $gadget; |
812 | | - // returns * '' if the gadget exists but doesn't have any preferences (or provided ones are not valid) |
813 | | - // * the preference description in JSON format, otherwise |
| 885 | + /** |
| 886 | + * Gets description of preferences for this gadget. |
| 887 | + * |
| 888 | + * @return Mixed null if the gadget exists but doesn't have any preferences or if provided ones are not valid, |
| 889 | + * an array with the description of preferences otherwise. |
| 890 | + */ |
814 | 891 | public function getPrefsDescription() { |
815 | | - $prefsMsg = "Gadget-{$this->name}.preferences"; |
| 892 | + $prefsDescriptionMsg = "Gadget-{$this->name}.preferences"; |
816 | 893 | |
817 | 894 | //TODO: use cache |
818 | 895 | |
819 | | - $prefsJson = wfMsgForContentNoTrans( $prefsMsg ); |
820 | | - if ( wfEmptyMsg( $prefsMsg, $prefsJson ) || |
821 | | - !self::isGadgetPrefsDescriptionValid( $prefsJson ) ) |
| 896 | + $prefsDescriptionJson = wfMsgForContentNoTrans( $prefsDescriptionMsg ); |
| 897 | + if ( wfEmptyMsg( $prefsDescriptionMsg, $prefsDescriptionJson ) || |
| 898 | + !self::isGadgetPrefsDescriptionValid( $prefsDescriptionJson ) ) |
822 | 899 | { |
823 | | - return ''; |
| 900 | + return null; |
824 | 901 | } |
825 | 902 | |
826 | | - return $prefsJson; |
| 903 | + return FormatJson::decode( $prefsDescriptionJson, true ); |
827 | 904 | } |
828 | 905 | |
829 | 906 | //Check if a preference is valid, according to description |
830 | 907 | //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName]) |
831 | 908 | // to allow checking for null. |
832 | 909 | private static function checkSinglePref( &$prefDescription, &$prefs, $prefName ) { |
833 | | - |
| 910 | + |
834 | 911 | //isset( $prefs[$prefName] ) would return false for null values |
835 | 912 | if ( !array_key_exists( $prefName, $prefs ) ) { |
836 | 913 | return false; |
— | — | @@ -911,8 +988,15 @@ |
912 | 989 | } |
913 | 990 | } |
914 | 991 | |
915 | | - //Returns true if $prefs is an array of preferences that passes validation |
916 | | - private static function checkPrefsAgainstDescription( &$prefsDescription, &$prefs ) { |
| 992 | + /** |
| 993 | + * Checks if $prefs is an array of preferences that passes validation |
| 994 | + * |
| 995 | + * @param $prefsDescription Array: the preferences description to use. |
| 996 | + * @param &$prefs Array: reference of the array of preferences to check. |
| 997 | + * |
| 998 | + * @return boolean true if $prefs passes validation against $prefsDescription, false otherwise. |
| 999 | + */ |
| 1000 | + public static function checkPrefsAgainstDescription( $prefsDescription, $prefs ) { |
917 | 1001 | foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) { |
918 | 1002 | if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) { |
919 | 1003 | return false; |
— | — | @@ -921,9 +1005,14 @@ |
922 | 1006 | return true; |
923 | 1007 | } |
924 | 1008 | |
925 | | - //Fixes $prefs so that it matches the description given by $prefsDescription. |
926 | | - //All values of $prefs that fail validation are replaced with default values. |
927 | | - private static function matchPrefsWithDescription( &$prefsDescription, &$prefs ) { |
| 1009 | + /** |
| 1010 | + * Fixes $prefs so that it matches the description given by $prefsDescription. |
| 1011 | + * All values of $prefs that fail validation are replaced with default values. |
| 1012 | + * |
| 1013 | + * @param $prefsDescription Array: the preferences description to use. |
| 1014 | + * @param &$prefs Array: reference of the array of preferences to match. |
| 1015 | + */ |
| 1016 | + public static function matchPrefsWithDescription( $prefsDescription, &$prefs ) { |
928 | 1017 | //Remove unexisting preferences from $prefs |
929 | 1018 | foreach ( $prefs as $prefName => $value ) { |
930 | 1019 | if ( !isset( $prefsDescription['fields'][$prefName] ) ) { |
— | — | @@ -939,107 +1028,52 @@ |
940 | 1029 | } |
941 | 1030 | } |
942 | 1031 | |
943 | | - //Get user's preferences for this gadget |
944 | | - public function getUserPrefs( $user ) { |
945 | | - //TODO: cache! |
| 1032 | + /** |
| 1033 | + * Returns current user's preferences for this gadget. |
| 1034 | + * |
| 1035 | + * @return Mixed the array of preferences if they have been set, null otherwise. |
| 1036 | + */ |
| 1037 | + public function getPrefs() { |
| 1038 | + $prefs = self::$preferencesCache[$this->getName()]; |
| 1039 | + return self::$preferencesCache[$this->getName()]; |
| 1040 | + } |
946 | 1041 | |
947 | | - $prefsDescriptionJson = $this->getPrefsDescription(); |
| 1042 | + /** |
| 1043 | + * Sets current user's preferences for this gadget, after validating them. |
| 1044 | + * |
| 1045 | + * @param $prefs Array: the array of preferences. |
| 1046 | + * @param $savePrefs boolean: if true, preferences are also saved back to the Database. |
| 1047 | + * |
| 1048 | + * @return boolean: true if validation is passed, false otherwise. |
| 1049 | + */ |
| 1050 | + public function setPrefs( $prefs, $savePrefs = false ) { |
948 | 1051 | |
949 | | - if ( $prefsDescriptionJson === '' ) { |
950 | | - return null; |
| 1052 | + if ( is_string( $prefs ) ) { |
| 1053 | + $prefs = FormatJson::decode( $prefs, true ); |
951 | 1054 | } |
952 | 1055 | |
953 | | - $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true ); |
954 | | - |
955 | | - |
956 | | - $dbr = wfGetDB( DB_SLAVE ); |
957 | | - |
958 | | - $id = $user->getId(); |
959 | | - $property = "gadget-{$this->name}-config"; |
960 | | - |
961 | | - $res = $dbr->selectRow( |
962 | | - 'user_properties', |
963 | | - array('up_value'), |
964 | | - array( |
965 | | - "up_user={$id}", |
966 | | - "up_property='{$property}'" |
967 | | - ), |
968 | | - __METHOD__); |
969 | | - |
970 | | - |
971 | | - if ( !$res ) { |
972 | | - $userPrefs = array(); //No prefs in DB, will just get defaults |
973 | | - } else { |
974 | | - $userPrefsJson = $res->up_value; |
975 | | - $userPrefs = FormatJson::decode( $userPrefsJson, true ); |
| 1056 | + if ( $prefs === null || !is_array( $prefs ) ) { |
| 1057 | + throw new MWException( __METHOD__ . ': $prefs must be an array or valid JSON' ); |
976 | 1058 | } |
977 | | - |
978 | | - self::matchPrefsWithDescription( $prefsDescription, $userPrefs ); |
979 | | - |
980 | | - return $userPrefs; |
981 | | - } |
982 | 1059 | |
983 | | - //Set user's preferences for this gadget. |
984 | | - //Returns false if preferences are rejected (that is, they don't pass validation) |
985 | | - public function setUserPrefs( $user, &$preferences ) { |
986 | | - $prefsDescriptionJson = $this->getPrefsDescription(); |
| 1060 | + $prefsDescription = $this->getPrefsDescription(); |
987 | 1061 | |
988 | | - if ( $prefsDescriptionJson === '' ) { |
| 1062 | + if ( $prefsDescription === null ) { |
989 | 1063 | return false; //nothing to save |
990 | 1064 | } |
991 | 1065 | |
992 | | - $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true ); |
993 | | - |
994 | | - if ( !self::checkPrefsAgainstDescription( $prefsDescription, $preferences ) ) { |
| 1066 | + if ( !self::checkPrefsAgainstDescription( $prefsDescription, $prefs ) ) { |
995 | 1067 | return false; //validation failed |
996 | 1068 | } |
997 | 1069 | |
998 | | - //TODO: should remove preferences that are equal to their default? |
999 | | - |
1000 | | - //Save preferences to DB |
1001 | | - |
1002 | | - $preferencesJson = FormatJson::encode( $preferences ); |
1003 | | - |
1004 | | - $dbw = wfGetDB( DB_MASTER ); |
1005 | | - |
1006 | | - $id = $user->getId(); |
1007 | | - $property = "gadget-{$this->name}-config"; |
1008 | | - |
1009 | | - $row = array( |
1010 | | - 'up_user' => $id, |
1011 | | - 'up_property' => $property, |
1012 | | - 'up_value' => $preferencesJson |
1013 | | - ); |
1014 | | - |
1015 | | - //Could probably be done with the "ON DUPLICATE KEY UPDATE" syntax |
1016 | | - |
1017 | | - $res = $dbw->update( |
1018 | | - 'user_properties', |
1019 | | - $row, |
1020 | | - array( |
1021 | | - "up_user={$id}", |
1022 | | - "up_property='{$property}'" |
1023 | | - ), |
1024 | | - __METHOD__ |
1025 | | - ); |
| 1070 | + self::$preferencesCache[$this->getName()] = $prefs; |
1026 | 1071 | |
1027 | | - |
1028 | | - $rc = $dbw->affectedRows(); |
1029 | | - if ( $rc == 0 ) { |
1030 | | - $dbw->insert( |
1031 | | - 'user_properties', |
1032 | | - $row, |
1033 | | - __METHOD__, |
1034 | | - array( 'IGNORE' ) //ignore insertions without any changes |
1035 | | - ); |
| 1072 | + if ( $savePrefs ) { |
| 1073 | + $user = RequestContext::getMain()->getUser(); |
| 1074 | + $user->saveSettings(); |
1036 | 1075 | } |
1037 | | - |
1038 | | - //Invalidate cache and update user_touched |
1039 | | - $user->invalidateCache( true ); |
1040 | | - |
1041 | 1076 | return true; |
1042 | 1077 | } |
1043 | | - |
1044 | 1078 | } |
1045 | 1079 | |
1046 | 1080 | /** |
— | — | @@ -1085,10 +1119,8 @@ |
1086 | 1120 | $gadgets = Gadget::loadList(); |
1087 | 1121 | $gadget = $gadgets[$gadgetName]; |
1088 | 1122 | |
1089 | | - $user = RequestContext::getMain()->getUser(); |
| 1123 | + $prefs = $gadget->getPrefs(); |
1090 | 1124 | |
1091 | | - $prefs = $gadget->getUserPrefs( $user ); |
1092 | | - |
1093 | 1125 | //Enclose gadget's code in a closure, with "this" bound to the |
1094 | 1126 | //configuration object (or to "window" for non-configurable gadgets) |
1095 | 1127 | $header = '(function(){'; |
— | — | @@ -1114,7 +1146,7 @@ |
1115 | 1147 | public function getModifiedTime( ResourceLoaderContext $context ) { |
1116 | 1148 | $touched = RequestContext::getMain()->getUser()->getTouched(); |
1117 | 1149 | |
1118 | | - return max( parent::getModifiedTime( $context ), $touched ); |
| 1150 | + return max( parent::getModifiedTime( $context ), wfTimestamp( TS_UNIX, $touched ) ); |
1119 | 1151 | } |
1120 | 1152 | } |
1121 | 1153 | |
— | — | @@ -1129,7 +1161,7 @@ |
1130 | 1162 | foreach ( $gadgetsList as $section => $gadgets ) { |
1131 | 1163 | foreach ( $gadgets as $gadgetName => $gadget ) { |
1132 | 1164 | $prefs = $gadget->getPrefsDescription(); |
1133 | | - if ( $prefs !== '' ) { |
| 1165 | + if ( $prefs !== null ) { |
1134 | 1166 | $configurableGadgets[] = $gadget->getName(); |
1135 | 1167 | } |
1136 | 1168 | } |
— | — | @@ -1140,5 +1172,3 @@ |
1141 | 1173 | return $script; |
1142 | 1174 | } |
1143 | 1175 | } |
1144 | | - |
1145 | | - |
Index: branches/salvatoreingala/Gadgets/Gadgets.php |
— | — | @@ -35,6 +35,7 @@ |
36 | 36 | $wgHooks['ResourceLoaderRegisterModules'][] = 'GadgetHooks::registerModules'; |
37 | 37 | $wgHooks['UnitTestsList'][] = 'GadgetHooks::unitTestsList'; |
38 | 38 | $wgHooks['UserLoadOptions'][] = 'GadgetHooks::userLoadOptions'; |
| 39 | +$wgHooks['UserSaveOptions'][] = 'GadgetHooks::userSaveOptions'; |
39 | 40 | |
40 | 41 | $dir = dirname(__FILE__) . '/'; |
41 | 42 | $wgExtensionMessagesFiles['Gadgets'] = $dir . 'Gadgets.i18n.php'; |
Index: branches/salvatoreingala/Gadgets/ApiSetGadgetPrefs.php |
— | — | @@ -51,7 +51,7 @@ |
52 | 52 | $this->dieUsage( 'The \'pref\' parameter must be valid JSON', 'notjson' ); |
53 | 53 | } |
54 | 54 | |
55 | | - $result = $gadget->setUserPrefs( $user, $prefs ); |
| 55 | + $result = $gadget->setPrefs( $prefs, true ); |
56 | 56 | |
57 | 57 | if ( $result === true ) { |
58 | 58 | $this->getResult()->addValue( |
Index: branches/salvatoreingala/Gadgets/ApiGetGadgetPrefs.php |
— | — | @@ -39,14 +39,13 @@ |
40 | 40 | $this->dieUsage( 'Gadget not found', 'notfound' ); |
41 | 41 | } |
42 | 42 | |
43 | | - $prefsDescriptionJson = $gadget->getPrefsDescription(); |
44 | | - $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true ); |
| 43 | + $prefsDescription = $gadget->getPrefsDescription(); |
45 | 44 | |
46 | 45 | if ( $prefsDescription === null ) { |
47 | 46 | $this->dieUsage( 'Gadget ' . $gadget->getName() . ' does not have any preference.', 'noprefs' ); |
48 | 47 | } |
49 | 48 | |
50 | | - $userPrefs = $gadget->getUserPrefs( $user ); |
| 49 | + $userPrefs = $gadget->getPrefs(); |
51 | 50 | |
52 | 51 | //Add user preferences to preference description |
53 | 52 | foreach ( $userPrefs as $pref => $value ) { |