| Index: trunk/phase3/includes/api/ApiEditPage.php |
| — | — | @@ -270,11 +270,11 @@ |
| 271 | 271 | $oldRequest = $wgRequest; |
| 272 | 272 | $wgRequest = $req; |
| 273 | 273 | |
| 274 | | - $retval = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] ); |
| | 274 | + $status = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] ); |
| 275 | 275 | $wgRequest = $oldRequest; |
| 276 | 276 | global $wgMaxArticleSize; |
| 277 | 277 | |
| 278 | | - switch( $retval ) { |
| | 278 | + switch( $status->value ) { |
| 279 | 279 | case EditPage::AS_HOOK_ERROR: |
| 280 | 280 | case EditPage::AS_HOOK_ERROR_EXPECTED: |
| 281 | 281 | $this->dieUsageMsg( 'hookaborted' ); |
| — | — | @@ -353,15 +353,12 @@ |
| 354 | 354 | $this->dieUsageMsg( 'summaryrequired' ); |
| 355 | 355 | |
| 356 | 356 | case EditPage::AS_END: |
| 357 | | - // This usually means some kind of race condition |
| 358 | | - // or DB weirdness occurred. Fall through to throw an unknown |
| 359 | | - // error. |
| 360 | | - |
| 361 | | - // This needs fixing higher up, as EditPage::internalAttemptSave |
| 362 | | - // should return the Status object, so that specific error |
| 363 | | - // conditions can be returned |
| | 357 | + // $status came from WikiPage::doEdit() |
| | 358 | + $errors = $status->getErrorsArray(); |
| | 359 | + $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map |
| | 360 | + break; |
| 364 | 361 | default: |
| 365 | | - $this->dieUsageMsg( array( 'unknownerror', $retval ) ); |
| | 362 | + $this->dieUsageMsg( array( 'unknownerror', $status->value ) ); |
| 366 | 363 | } |
| 367 | 364 | $apiResult->addValue( null, $this->getModuleName(), $r ); |
| 368 | 365 | } |
| Index: trunk/phase3/includes/api/ApiBase.php |
| — | — | @@ -1164,6 +1164,12 @@ |
| 1165 | 1165 | 'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ), |
| 1166 | 1166 | 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''" ), |
| 1167 | 1167 | 'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ), |
| | 1168 | + |
| | 1169 | + // Messages from WikiPage::doEit() |
| | 1170 | + 'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ), |
| | 1171 | + 'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ), |
| | 1172 | + 'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ), |
| | 1173 | + 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ), |
| 1168 | 1174 | |
| 1169 | 1175 | // uploadMsgs |
| 1170 | 1176 | 'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ), |
| Index: trunk/phase3/includes/EditPage.php |
| — | — | @@ -853,32 +853,42 @@ |
| 854 | 854 | * @param $result |
| 855 | 855 | * @param $bot bool |
| 856 | 856 | * |
| 857 | | - * @return int one of the constants describing the result |
| | 857 | + * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value, |
| | 858 | + * |
| | 859 | + * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are |
| | 860 | + * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g. |
| | 861 | + * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time. |
| 858 | 862 | */ |
| 859 | 863 | function internalAttemptSave( &$result, $bot = false ) { |
| 860 | 864 | global $wgFilterCallback, $wgUser, $wgRequest, $wgParser; |
| 861 | 865 | global $wgMaxArticleSize; |
| | 866 | + |
| | 867 | + $status = Status::newGood(); |
| 862 | 868 | |
| 863 | 869 | wfProfileIn( __METHOD__ ); |
| 864 | 870 | wfProfileIn( __METHOD__ . '-checks' ); |
| 865 | 871 | |
| 866 | 872 | if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) { |
| 867 | 873 | wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" ); |
| | 874 | + $status->fatal( 'hookaborted' ); |
| | 875 | + $status->value = self::AS_HOOK_ERROR; |
| 868 | 876 | wfProfileOut( __METHOD__ . '-checks' ); |
| 869 | 877 | wfProfileOut( __METHOD__ ); |
| 870 | | - return self::AS_HOOK_ERROR; |
| | 878 | + return $status; |
| 871 | 879 | } |
| 872 | 880 | |
| 873 | 881 | # Check image redirect |
| 874 | 882 | if ( $this->mTitle->getNamespace() == NS_FILE && |
| 875 | 883 | Title::newFromRedirect( $this->textbox1 ) instanceof Title && |
| 876 | 884 | !$wgUser->isAllowed( 'upload' ) ) { |
| 877 | | - $isAnon = $wgUser->isAnon(); |
| | 885 | + $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; |
| | 886 | + $status->setResult( false, $code ); |
| 878 | 887 | |
| 879 | 888 | wfProfileOut( __METHOD__ . '-checks' ); |
| | 889 | + |
| 880 | 890 | wfProfileOut( __METHOD__ ); |
| 881 | 891 | |
| 882 | | - return $isAnon ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; |
| | 892 | + return $status; |
| 883 | 893 | } |
| 884 | 894 | |
| 885 | 895 | # Check for spam |
| — | — | @@ -892,71 +902,88 @@ |
| 893 | 903 | $pdbk = $this->mTitle->getPrefixedDBkey(); |
| 894 | 904 | $match = str_replace( "\n", '', $match ); |
| 895 | 905 | wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" ); |
| | 906 | + $status->fatal( 'spamprotectionmatch', $match ); |
| | 907 | + $status->value = self::AS_SPAM_ERROR; |
| 896 | 908 | wfProfileOut( __METHOD__ . '-checks' ); |
| 897 | 909 | wfProfileOut( __METHOD__ ); |
| 898 | | - return self::AS_SPAM_ERROR; |
| | 910 | + return $status; |
| 899 | 911 | } |
| 900 | 912 | if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) { |
| 901 | 913 | # Error messages or other handling should be performed by the filter function |
| | 914 | + $status->setResult( false, self::AS_FILTERING ); |
| 902 | 915 | wfProfileOut( __METHOD__ . '-checks' ); |
| 903 | 916 | wfProfileOut( __METHOD__ ); |
| 904 | | - return self::AS_FILTERING; |
| | 917 | + return $status; |
| 905 | 918 | } |
| 906 | 919 | if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) { |
| 907 | 920 | # Error messages etc. could be handled within the hook... |
| | 921 | + $status->fatal( 'hookaborted' ); |
| | 922 | + $status->value = self::AS_HOOK_ERROR; |
| 908 | 923 | wfProfileOut( __METHOD__ . '-checks' ); |
| 909 | 924 | wfProfileOut( __METHOD__ ); |
| 910 | | - return self::AS_HOOK_ERROR; |
| | 925 | + return $status; |
| 911 | 926 | } elseif ( $this->hookError != '' ) { |
| 912 | 927 | # ...or the hook could be expecting us to produce an error |
| | 928 | + $status->fatal( 'hookaborted' ); |
| | 929 | + $status->value = self::AS_HOOK_ERROR_EXPECTED; |
| 913 | 930 | wfProfileOut( __METHOD__ . '-checks' ); |
| 914 | 931 | wfProfileOut( __METHOD__ ); |
| 915 | | - return self::AS_HOOK_ERROR_EXPECTED; |
| | 932 | + return $status; |
| 916 | 933 | } |
| 917 | 934 | if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) { |
| 918 | 935 | # Check block state against master, thus 'false'. |
| | 936 | + $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER ); |
| 919 | 937 | wfProfileOut( __METHOD__ . '-checks' ); |
| 920 | 938 | wfProfileOut( __METHOD__ ); |
| 921 | | - return self::AS_BLOCKED_PAGE_FOR_USER; |
| | 939 | + return $status; |
| 922 | 940 | } |
| 923 | 941 | $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 ); |
| 924 | 942 | if ( $this->kblength > $wgMaxArticleSize ) { |
| 925 | 943 | // Error will be displayed by showEditForm() |
| 926 | 944 | $this->tooBig = true; |
| | 945 | + $status->setResult( false, self::AS_CONTENT_TOO_BIG ); |
| 927 | 946 | wfProfileOut( __METHOD__ . '-checks' ); |
| 928 | 947 | wfProfileOut( __METHOD__ ); |
| 929 | | - return self::AS_CONTENT_TOO_BIG; |
| | 948 | + return $status; |
| 930 | 949 | } |
| 931 | 950 | |
| 932 | 951 | if ( !$wgUser->isAllowed( 'edit' ) ) { |
| 933 | 952 | if ( $wgUser->isAnon() ) { |
| | 953 | + $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON ); |
| 934 | 954 | wfProfileOut( __METHOD__ . '-checks' ); |
| 935 | 955 | wfProfileOut( __METHOD__ ); |
| 936 | | - return self::AS_READ_ONLY_PAGE_ANON; |
| | 956 | + return $status; |
| 937 | 957 | } else { |
| | 958 | + $status->fatal( 'readonlytext' ); |
| | 959 | + $status->value = self::AS_READ_ONLY_PAGE_LOGGED; |
| 938 | 960 | wfProfileOut( __METHOD__ . '-checks' ); |
| 939 | 961 | wfProfileOut( __METHOD__ ); |
| 940 | | - return self::AS_READ_ONLY_PAGE_LOGGED; |
| | 962 | + return $status; |
| 941 | 963 | } |
| 942 | 964 | } |
| 943 | 965 | |
| 944 | 966 | if ( wfReadOnly() ) { |
| | 967 | + $status->fatal( 'readonlytext' ); |
| | 968 | + $status->value = self::AS_READ_ONLY_PAGE; |
| 945 | 969 | wfProfileOut( __METHOD__ . '-checks' ); |
| 946 | 970 | wfProfileOut( __METHOD__ ); |
| 947 | | - return self::AS_READ_ONLY_PAGE; |
| | 971 | + return $status; |
| 948 | 972 | } |
| 949 | 973 | if ( $wgUser->pingLimiter() ) { |
| | 974 | + $status->fatal( 'actionthrottledtext' ); |
| | 975 | + $status->value = self::AS_RATE_LIMITED; |
| 950 | 976 | wfProfileOut( __METHOD__ . '-checks' ); |
| 951 | 977 | wfProfileOut( __METHOD__ ); |
| 952 | | - return self::AS_RATE_LIMITED; |
| | 978 | + return $status; |
| 953 | 979 | } |
| 954 | 980 | |
| 955 | 981 | # If the article has been deleted while editing, don't save it without |
| 956 | 982 | # confirmation |
| 957 | 983 | if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) { |
| | 984 | + $status->setResult( false, self::AS_ARTICLE_WAS_DELETED ); |
| 958 | 985 | wfProfileOut( __METHOD__ . '-checks' ); |
| 959 | 986 | wfProfileOut( __METHOD__ ); |
| 960 | | - return self::AS_ARTICLE_WAS_DELETED; |
| | 987 | + return $status; |
| 961 | 988 | } |
| 962 | 989 | |
| 963 | 990 | wfProfileOut( __METHOD__ . '-checks' ); |
| — | — | @@ -968,34 +995,43 @@ |
| 969 | 996 | if ( $new ) { |
| 970 | 997 | // Late check for create permission, just in case *PARANOIA* |
| 971 | 998 | if ( !$this->mTitle->userCan( 'create' ) ) { |
| | 999 | + $status->fatal( 'nocreatetext' ); |
| | 1000 | + $status->value = self::AS_NO_CREATE_PERMISSION; |
| 972 | 1001 | wfDebug( __METHOD__ . ": no create permission\n" ); |
| 973 | 1002 | wfProfileOut( __METHOD__ ); |
| 974 | | - return self::AS_NO_CREATE_PERMISSION; |
| | 1003 | + return $status; |
| 975 | 1004 | } |
| 976 | 1005 | |
| 977 | 1006 | # Don't save a new article if it's blank. |
| 978 | 1007 | if ( $this->textbox1 == '' ) { |
| | 1008 | + $status->setResult( false, self::AS_BLANK_ARTICLE ); |
| 979 | 1009 | wfProfileOut( __METHOD__ ); |
| 980 | | - return self::AS_BLANK_ARTICLE; |
| | 1010 | + return $status; |
| 981 | 1011 | } |
| 982 | 1012 | |
| 983 | 1013 | // Run post-section-merge edit filter |
| 984 | 1014 | if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) { |
| 985 | 1015 | # Error messages etc. could be handled within the hook... |
| | 1016 | + $status->fatal( 'hookaborted' ); |
| | 1017 | + $status->value = self::AS_HOOK_ERROR; |
| 986 | 1018 | wfProfileOut( __METHOD__ ); |
| 987 | | - return self::AS_HOOK_ERROR; |
| | 1019 | + return $status; |
| 988 | 1020 | } elseif ( $this->hookError != '' ) { |
| 989 | 1021 | # ...or the hook could be expecting us to produce an error |
| | 1022 | + $status->fatal( 'hookaborted' ); |
| | 1023 | + $status->value = self::AS_HOOK_ERROR_EXPECTED; |
| 990 | 1024 | wfProfileOut( __METHOD__ ); |
| 991 | | - return self::AS_HOOK_ERROR_EXPECTED; |
| | 1025 | + return $status; |
| 992 | 1026 | } |
| 993 | 1027 | |
| 994 | 1028 | # Handle the user preference to force summaries here. Check if it's not a redirect. |
| 995 | 1029 | if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) { |
| 996 | 1030 | if ( md5( $this->summary ) == $this->autoSumm ) { |
| 997 | 1031 | $this->missingSummary = true; |
| | 1032 | + $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh |
| | 1033 | + $status->value = self::AS_SUMMARY_NEEDED; |
| 998 | 1034 | wfProfileOut( __METHOD__ ); |
| 999 | | - return self::AS_SUMMARY_NEEDED; |
| | 1035 | + return $status; |
| 1000 | 1036 | } |
| 1001 | 1037 | } |
| 1002 | 1038 | |
| — | — | @@ -1004,7 +1040,7 @@ |
| 1005 | 1041 | $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text; |
| 1006 | 1042 | } |
| 1007 | 1043 | |
| 1008 | | - $retval = self::AS_SUCCESS_NEW_ARTICLE; |
| | 1044 | + $status->value = self::AS_SUCCESS_NEW_ARTICLE; |
| 1009 | 1045 | |
| 1010 | 1046 | } else { |
| 1011 | 1047 | |
| — | — | @@ -1061,19 +1097,24 @@ |
| 1062 | 1098 | } |
| 1063 | 1099 | |
| 1064 | 1100 | if ( $this->isConflict ) { |
| | 1101 | + $status->setResult( false, self::AS_CONFLICT_DETECTED ); |
| 1065 | 1102 | wfProfileOut( __METHOD__ ); |
| 1066 | | - return self::AS_CONFLICT_DETECTED; |
| | 1103 | + return $status; |
| 1067 | 1104 | } |
| 1068 | 1105 | |
| 1069 | 1106 | // Run post-section-merge edit filter |
| 1070 | 1107 | if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) { |
| 1071 | 1108 | # Error messages etc. could be handled within the hook... |
| | 1109 | + $status->fatal( 'hookaborted' ); |
| | 1110 | + $status->value = self::AS_HOOK_ERROR; |
| 1072 | 1111 | wfProfileOut( __METHOD__ ); |
| 1073 | | - return self::AS_HOOK_ERROR; |
| | 1112 | + return $status; |
| 1074 | 1113 | } elseif ( $this->hookError != '' ) { |
| 1075 | 1114 | # ...or the hook could be expecting us to produce an error |
| | 1115 | + $status->fatal( 'hookaborted' ); |
| | 1116 | + $status->value = self::AS_HOOK_ERROR_EXPECTED; |
| 1076 | 1117 | wfProfileOut( __METHOD__ ); |
| 1077 | | - return self::AS_HOOK_ERROR_EXPECTED; |
| | 1118 | + return $status; |
| 1078 | 1119 | } |
| 1079 | 1120 | |
| 1080 | 1121 | # Handle the user preference to force summaries here, but not for null edits |
| — | — | @@ -1092,8 +1133,10 @@ |
| 1093 | 1134 | if ( $this->section == 'new' && !$this->allowBlankSummary ) { |
| 1094 | 1135 | if ( trim( $this->summary ) == '' ) { |
| 1095 | 1136 | $this->missingSummary = true; |
| | 1137 | + $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh |
| | 1138 | + $status->value = self::AS_SUMMARY_NEEDED; |
| 1096 | 1139 | wfProfileOut( __METHOD__ ); |
| 1097 | | - return self::AS_SUMMARY_NEEDED; |
| | 1140 | + return $status; |
| 1098 | 1141 | } |
| 1099 | 1142 | } |
| 1100 | 1143 | |
| — | — | @@ -1103,9 +1146,11 @@ |
| 1104 | 1147 | if ( $this->section == 'new' ) { |
| 1105 | 1148 | if ( $this->textbox1 == '' ) { |
| 1106 | 1149 | $this->missingComment = true; |
| | 1150 | + $status->fatal( 'missingcommenttext' ); |
| | 1151 | + $status->value = self::AS_TEXTBOX_EMPTY; |
| 1107 | 1152 | wfProfileOut( __METHOD__ . '-sectionanchor' ); |
| 1108 | 1153 | wfProfileOut( __METHOD__ ); |
| 1109 | | - return self::AS_TEXTBOX_EMPTY; |
| | 1154 | + return $status; |
| 1110 | 1155 | } |
| 1111 | 1156 | if ( $this->summary != '' ) { |
| 1112 | 1157 | $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); |
| — | — | @@ -1135,15 +1180,16 @@ |
| 1136 | 1181 | $this->textbox1 = $text; |
| 1137 | 1182 | $this->section = ''; |
| 1138 | 1183 | |
| 1139 | | - $retval = self::AS_SUCCESS_UPDATE; |
| | 1184 | + $status->value = self::AS_SUCCESS_UPDATE; |
| 1140 | 1185 | } |
| 1141 | 1186 | |
| 1142 | 1187 | // Check for length errors again now that the section is merged in |
| 1143 | 1188 | $this->kblength = (int)( strlen( $text ) / 1024 ); |
| 1144 | 1189 | if ( $this->kblength > $wgMaxArticleSize ) { |
| 1145 | 1190 | $this->tooBig = true; |
| | 1191 | + $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED ); |
| 1146 | 1192 | wfProfileOut( __METHOD__ ); |
| 1147 | | - return self::AS_MAX_ARTICLE_SIZE_EXCEEDED; |
| | 1193 | + return $status; |
| 1148 | 1194 | } |
| 1149 | 1195 | |
| 1150 | 1196 | $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | |
| — | — | @@ -1151,17 +1197,18 @@ |
| 1152 | 1198 | ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | |
| 1153 | 1199 | ( $bot ? EDIT_FORCE_BOT : 0 ); |
| 1154 | 1200 | |
| 1155 | | - $status = $this->mArticle->doEdit( $text, $this->summary, $flags ); |
| | 1201 | + $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags ); |
| 1156 | 1202 | |
| 1157 | | - if ( $status->isOK() ) { |
| | 1203 | + if ( $doEditStatus->isOK() ) { |
| 1158 | 1204 | $result['redirect'] = Title::newFromRedirect( $text ) !== null; |
| 1159 | 1205 | $this->commitWatch(); |
| 1160 | 1206 | wfProfileOut( __METHOD__ ); |
| 1161 | | - return $retval; |
| | 1207 | + return $status; |
| 1162 | 1208 | } else { |
| 1163 | 1209 | $this->isConflict = true; |
| | 1210 | + $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares |
| 1164 | 1211 | wfProfileOut( __METHOD__ ); |
| 1165 | | - return self::AS_END; |
| | 1212 | + return $doEditStatus; |
| 1166 | 1213 | } |
| 1167 | 1214 | } |
| 1168 | 1215 | |
| — | — | @@ -2833,13 +2880,14 @@ |
| 2834 | 2881 | $resultDetails = false; |
| 2835 | 2882 | # Allow bots to exempt some edits from bot flagging |
| 2836 | 2883 | $bot = $wgUser->isAllowed( 'bot' ) && $this->bot; |
| 2837 | | - $value = $this->internalAttemptSave( $resultDetails, $bot ); |
| | 2884 | + $status = $this->internalAttemptSave( $resultDetails, $bot ); |
| | 2885 | + // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status |
| 2838 | 2886 | |
| 2839 | | - if ( $value == self::AS_SUCCESS_UPDATE || $value == self::AS_SUCCESS_NEW_ARTICLE ) { |
| | 2887 | + if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) { |
| 2840 | 2888 | $this->didSave = true; |
| 2841 | 2889 | } |
| 2842 | 2890 | |
| 2843 | | - switch ( $value ) { |
| | 2891 | + switch ( $status->value ) { |
| 2844 | 2892 | case self::AS_HOOK_ERROR_EXPECTED: |
| 2845 | 2893 | case self::AS_CONTENT_TOO_BIG: |
| 2846 | 2894 | case self::AS_ARTICLE_WAS_DELETED: |