__FILE__, 'name' => 'TrustedLinks', 'author' => 'James Paige and Laird Shaw', 'url' => 'https://www.mediawiki.org/wiki/Extension:NotEvil', 'descriptionmsg' => 'trustedlinks-desc', 'version' => '1.0', ); class TrustedLinks { // From http://daringfireball.net/2010/07/improved_regex_for_matching_urls, with modifications // to avoid matching internal wiki links: we limit protocols to http(s) and (s)ftp. static $defaultUrlRegex = '#(?i)\\b((?:(?:http(s)?|(s)?ftp)://|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?«»“”‘’]))#'; /** * The entry point to the extension. Implements the EditFilter hook. */ static function OnEditFilter($EditPage, $newText, $section, &$hookError, $summary) { global $wgUser; $article = $EditPage->getArticle(); $oldText = $article->getRawText(); $addedUrls = self::getNewUrls($oldText, $newText); if ($addedUrls) { $mayPostUrls = $wgUser->isAllowed('postlink'); if (!$mayPostUrls) { $errorMessage = ''; // A hook function return of false means to abort our planned cancellation of // the page save. A return of true means to continue our planned cancellation of // the page save, optionally with an error message generated by the hook // function. if (wfRunHooks('UntrustedLinkAttempt', array($EditPage, $addedUrls, $newText, &$errorMessage))) { if (!$errorMessage) { if (function_exists('wfLoadExtensionMessages')) { wfLoadExtensionMessages('TrustedLinks'); } $wfMsgFunc = function_exists('wfMessage') ? 'wfMessage' : 'wfMsg'; $errorMessage = $wfMsgFunc('trustedlinks_noright'); } // Setting $hookError indicates failure even though we're returning // true, and for the core code to display $hookError as an error message. $hookError = $errorMessage; } } } return true; } /** * Returns true if a URL or URL-like text is present in the string $newText * that was not present in the string $oldText, and false otherwise. */ static function wasLinkAdded($oldText, $newText) { return self::getNewUrls($oldText, $newText) ? true : false; } /** * Returns a complete list of unique URLs or URL-like texts present in * the string $newText that were not present in the string $oldText. */ static function getNewUrls($oldText, $newText) { $oldTextArr = explode("\n", $oldText); $newTextArr = explode("\n", $newText); $newOrChangedLines = self::getNewOrChangedLines($oldTextArr, $newTextArr); $urlsInNewOrChangedLines = self::extractUrls($newOrChangedLines); if (!$urlsInNewOrChangedLines) { return array(); } else { $preExistingUrls = self::extractUrls($oldTextArr); return array_diff($urlsInNewOrChangedLines, $preExistingUrls); } } /** * Returns a complete list of unique URLs or URL-like texts present in * the array of strings that is $lines. Matches are performed against * $wgTrustedLinksRegex if set, otherwise a default regex. */ static function extractUrls($lines) { global $wgTrustedLinksRegex; $urlRegex = isset($wgTrustedLinksRegex) ? $wgTrustedLinksRegex : self::$defaultUrlRegex; $urls = array(); foreach ($lines as $line) { if (preg_match_all($urlRegex, $line, $matches, PREG_PATTERN_ORDER)) { foreach ($matches[0] as $url) { if (!in_array($url, $urls)) $urls[] = $url; } } } return $urls; } /** * Returns an array of lines (strings) which are either new or changed * in the diff between the arrays of strings $oldTextArr and $newTextArr. */ static function getNewOrChangedLines($oldTextArr, $newTextArr) { $WikiDiff3 = new WikiDiff3(); $diffRanges = $WikiDiff3->diff_range($oldTextArr, $newTextArr); $newOrChangedLines = array(); foreach ($diffRanges as $diffRange) { for ($i = $diffRange->rightstart; $i < $diffRange->rightend; $i++) { $newOrChangedLines[] = $newTextArr[$i]; } } return $newOrChangedLines; } } ?>