├── .gitattributes
├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── Configs
├── redirector.cfg
├── sample.cfg
└── scriptdefault.cfg
├── GenFunctions.php
├── HTTP.php
├── Includes
├── AutoUpdate.php
├── Autoloader.php
├── Exceptions.php
├── Hooks.php
├── Image.php
├── Page.php
├── Peachy.php
├── PeachyAWBFunctions.php
├── SSH.php
├── User.php
├── Wiki.php
├── XMLParse.php
├── lime.php
├── limeColorizer.php
├── limeCoverage.php
├── limeHarness.php
├── limeOutput.php
├── limeRegistration.php
└── lime_test.php
├── Init.php
├── LICENSE
├── Plugins
├── AbuseFilter.php
├── CheckUser.php
├── CodeReview.php
├── Email.php
├── FlaggedRevs.php
├── GlobalBlocking.php
├── GlobalUserInfo.php
├── IRC.php
├── RFA.php
├── RPED.php
├── SiteMatrix.php
└── Xml.php
├── README.md
├── Tests
├── GenFunctionsTest.php
├── Includes
│ ├── AutoUpdateTest.php
│ ├── PageTest.php
│ └── PeachyAWBFunctionsTest.php
├── bootstrap.php
└── phplint.sh
├── config.inc.php
├── phpdoc.dist.xml
└── phpunit.xml.dist
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | */phpunit.phar
2 |
3 |
4 | #################
5 | ## PhpStorm
6 | #################
7 | .idea/
8 |
9 | #################
10 | ## Eclipse
11 | #################
12 |
13 | *.pydevproject
14 | .project
15 | .metadata
16 | bin/
17 | tmp/
18 | *.tmp
19 | *.bak
20 | *.swp
21 | *~.nib
22 | local.properties
23 | .classpath
24 | .settings/
25 | .loadpath
26 |
27 | # External tool builders
28 | .externalToolBuilders/
29 |
30 | # Locally stored "Eclipse launch configurations"
31 | *.launch
32 |
33 | # CDT-specific
34 | .cproject
35 |
36 | # PDT-specific
37 | .buildpath
38 |
39 |
40 | #################
41 | ## Visual Studio
42 | #################
43 |
44 | ## Ignore Visual Studio temporary files, build results, and
45 | ## files generated by popular Visual Studio add-ons.
46 |
47 | # User-specific files
48 | *.suo
49 | *.user
50 | *.sln.docstates
51 |
52 | # Build results
53 |
54 | [Dd]ebug/
55 | [Rr]elease/
56 | x64/
57 | build/
58 | [Bb]in/
59 | [Oo]bj/
60 |
61 | # MSTest test Results
62 | [Tt]est[Rr]esult*/
63 | [Bb]uild[Ll]og.*
64 |
65 | *_i.c
66 | *_p.c
67 | *.ilk
68 | *.meta
69 | *.obj
70 | *.pch
71 | *.pdb
72 | *.pgc
73 | *.pgd
74 | *.rsp
75 | *.sbr
76 | *.tlb
77 | *.tli
78 | *.tlh
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.scc
86 |
87 | # Visual C++ cache files
88 | ipch/
89 | *.aps
90 | *.ncb
91 | *.opensdf
92 | *.sdf
93 | *.cachefile
94 |
95 | # Visual Studio profiler
96 | *.psess
97 | *.vsp
98 | *.vspx
99 |
100 | # Guidance Automation Toolkit
101 | *.gpState
102 |
103 | # ReSharper is a .NET coding add-in
104 | _ReSharper*/
105 | *.[Rr]e[Ss]harper
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | *.ncrunch*
115 | .*crunch*.local.xml
116 |
117 | # Installshield output folder
118 | [Ee]xpress/
119 |
120 | # DocProject is a documentation generator add-in
121 | DocProject/buildhelp/
122 | DocProject/Help/*.HxT
123 | DocProject/Help/*.HxC
124 | DocProject/Help/*.hhc
125 | DocProject/Help/*.hhk
126 | DocProject/Help/*.hhp
127 | DocProject/Help/Html2
128 | DocProject/Help/html
129 |
130 | # Click-Once directory
131 | publish/
132 |
133 | # Publish Web Output
134 | *.Publish.xml
135 | *.pubxml
136 |
137 | # NuGet Packages Directory
138 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
139 | #packages/
140 |
141 | # Windows Azure Build Output
142 | csx
143 | *.build.csdef
144 |
145 | # Windows Store app package directory
146 | AppPackages/
147 |
148 | # Others
149 | sql/
150 | *.Cache
151 | ClientBin/
152 | [Ss]tyle[Cc]op.*
153 | ~$*
154 | *~
155 | *.dbmdl
156 | *.[Pp]ublish.xml
157 | *.pfx
158 | *.publishsettings
159 |
160 | # RIA/Silverlight projects
161 | Generated_Code/
162 |
163 | # Backup & report files from converting an old project file to a newer
164 | # Visual Studio version. Backup files are not needed, because we have git ;-)
165 | _UpgradeReport_Files/
166 | Backup*/
167 | UpgradeLog*.XML
168 | UpgradeLog*.htm
169 |
170 | # SQL Server files
171 | App_Data/*.mdf
172 | App_Data/*.ldf
173 |
174 | #############
175 | ## Windows detritus
176 | #############
177 |
178 | # Windows image file caches
179 | Thumbs.db
180 | ehthumbs.db
181 |
182 | # Folder config file
183 | Desktop.ini
184 |
185 | # Recycle Bin used on file shares
186 | $RECYCLE.BIN/
187 |
188 | # Mac crap
189 | .DS_Store
190 |
191 |
192 | #############
193 | ## Python
194 | #############
195 |
196 | *.py[co]
197 |
198 | # Packages
199 | *.egg
200 | *.egg-info
201 | dist/
202 | eggs/
203 | parts/
204 | var/
205 | sdist/
206 | develop-eggs/
207 | .installed.cfg
208 |
209 | # Installer logs
210 | pip-log.txt
211 |
212 | # Unit test / coverage reports
213 | .coverage
214 | .tox
215 |
216 | #Translations
217 | *.mo
218 |
219 | #Mr Developer
220 | .mr.developer.cfg
221 |
222 | #peachy stuff
223 | *.cfg
224 | test.php
225 | acc.php
226 | config.local.inc.php
227 | Includes/updateversion
228 | Includes/SSHCore
229 | Includes/phpseclibupdate
230 |
231 | !Configs/redirector.cfg
232 | !Configs/sample.cfg
233 | !Configs/scriptdefault.cfg
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | inherit: true
2 |
3 | tools:
4 | php_code_sniffer: true
5 | php_cpd: true
6 | php_cs_fixer: true
7 | php_loc: true
8 | php_mess_detector: true
9 | php_pdepend: true
10 | php_analyzer: true
11 | sensiolabs_security_checker: true
12 | external_code_coverage: true
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.5
5 | - 5.4
6 | - 5.3
7 |
8 | script:
9 | - ./Tests/phplint.sh
10 | - phpunit --coverage-clover=coverage.clover
11 |
12 | after_script:
13 | - wget https://scrutinizer-ci.com/ocular.phar
14 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
15 |
16 | notifications:
17 | irc:
18 | channels:
19 | - "chat.freenode.net##add"
20 | on_success: change
21 | on_failure: always
--------------------------------------------------------------------------------
/Configs/redirector.cfg:
--------------------------------------------------------------------------------
1 | [config]
2 | useconfig = "path/to/another/config.cfg"
--------------------------------------------------------------------------------
/Configs/sample.cfg:
--------------------------------------------------------------------------------
1 | [config]
2 | baseurl = "https://en.wikipedia.org/w/api.php"
3 | username = "Example"
4 | password = "1234567890"
5 | maxlag = "0"
6 | editsperminute = "0"
7 | httpecho = "true"
8 | consumerkey = "foo"
9 | consumersecret = "foobar"
10 | accesstoken = "bla"
11 | accesssecret = "blabar"
12 | oauthurl = "https://en.wikipedia.org/w/index.php?title=Special:OAuth"
13 | method = "legacy"
--------------------------------------------------------------------------------
/Configs/scriptdefault.cfg:
--------------------------------------------------------------------------------
1 | [config]
2 | useconfig = "admin"
--------------------------------------------------------------------------------
/GenFunctions.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * @file
22 | * Stores general functions that do not belong in a class
23 | */
24 |
25 | /**
26 | * Case insensitive in_array function
27 | *
28 | * @param mixed $needle What to search for
29 | * @param array $haystack Array to search in
30 | * @param bool $strict
31 | * @return bool True if $needle is found in $haystack, case insensitive
32 | * @link http://us3.php.net/in_array
33 | */
34 | function iin_array( $needle, $haystack, $strict = false ) {
35 | return in_array_recursive( strtoupper_safe( $needle ), array_map( 'strtoupper_safe', $haystack ), $strict );
36 | }
37 |
38 | /**
39 | * @return string
40 | */
41 | function strtoupper_safe( $str ) {
42 | if( is_string( $str ) ) return strtoupper( $str );
43 | if( is_array( $str ) ) $str = array_map( 'strtoupper_safe', $str );
44 | return $str;
45 | }
46 |
47 | /**
48 | * Returns whether or not a string is found in another
49 | * Shortcut for strpos()
50 | *
51 | * @param string $needle What to search for
52 | * @param string $haystack What to search in
53 | * @param bool $insensitive Whether or not to do a case-insensitive search
54 | * @return bool True if $needle is found in $haystack
55 | * @link http://us3.php.net/strpos
56 | */
57 | function in_string( $needle, $haystack, $insensitive = false ) {
58 | $fnc = 'strpos';
59 | if( $insensitive ) $fnc = 'stripos';
60 |
61 | return $fnc( $haystack, $needle ) !== false;
62 | }
63 |
64 | /**
65 | * Recursive in_array function
66 | *
67 | * @param string $needle What to search for
68 | * @param array $haystack What to search in
69 | * @param bool $insensitive Whether or not to do a case-insensitive search
70 | * @return bool True if $needle is found in $haystack
71 | * @link http://us3.php.net/in_array
72 | */
73 | function in_array_recursive( $needle, $haystack, $insensitive = false ) {
74 | $fnc = 'in_array';
75 | if( $insensitive ) $fnc = 'iin_array';
76 |
77 | if( $fnc( $needle, $haystack ) ) return true;
78 | foreach( $haystack as $val ){
79 | if( is_array( $val ) ) {
80 | return in_array_recursive( $needle, $val );
81 | }
82 | }
83 | return false;
84 | }
85 |
86 | /**
87 | * Recursive glob() function.
88 | *
89 | * @access public
90 | * @param string $pattern . (default: '*')
91 | * @param int $flags . (default: 0)
92 | * @param string $path . (default: '')
93 | * @return array
94 | */
95 | function rglob( $pattern = '*', $flags = 0, $path = '' ) {
96 | $paths = glob( $path . '*', GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT );
97 | $files = glob( $path . $pattern, $flags );
98 | foreach( $paths as $path ){
99 | $files = array_merge( $files, rglob( $pattern, $flags, $path ) );
100 | }
101 | return $files;
102 | }
103 |
104 | /**
105 | * Detects the presence of a nobots template or one that denies editing by ours
106 | *
107 | * @access public
108 | * @param Wiki $wiki Wiki class
109 | * @param string $text Text of the page to check (default: '')
110 | * @param string $pgUsername Username to search for in the template (default: null)
111 | * @param string|null $optout Text to search for in the optout= parameter. (default: null)
112 | * @param string|null $taskname (default: null)
113 | * @return bool True on match of an appropriate nobots template
114 | */
115 | function checkExclusion( Wiki $wiki, $text = '', $pgUsername = null, $optout = null, $taskname = null ) {
116 | if( !$wiki->get_nobots() ) return false;
117 |
118 | if( in_string( "{{nobots}}", $text ) ) return true;
119 | if( in_string( "{{bots}}", $text ) ) return false;
120 |
121 | if( preg_match( '/\{\{bots\s*\|\s*allow\s*=\s*(.*?)\s*\}\}/i', $text, $allow ) ) {
122 | if( $allow[1] == "all" ) return false;
123 | if( $allow[1] == "none" ) return true;
124 | $allow = array_map( 'trim', explode( ',', $allow[1] ) );
125 | if( !is_null( $pgUsername ) && in_array( trim( $pgUsername ), $allow ) ) {
126 | return false;
127 | }
128 | return true;
129 | }
130 |
131 | if( preg_match( '/\{\{(no)?bots\s*\|\s*deny\s*=\s*(.*?)\s*\}\}/i', $text, $deny ) ) {
132 | if( $deny[2] == "all" ) return true;
133 | if( $deny[2] == "none" ) return false;
134 | $allow = array_map( 'trim', explode( ',', $deny[2] ) );
135 | if( ( !is_null( $pgUsername ) && in_array( trim( $pgUsername ), $allow ) ) || ( !is_null( $taskname ) && in_array( trim( $taskname ), $allow ) ) ) {
136 | return true;
137 | }
138 | return false;
139 | }
140 |
141 | if( !is_null( $optout ) && preg_match( '/\{\{(no)?bots\s*\|\s*optout\s*=\s*(.*?)\s*\}\}/i', $text, $allow ) ) {
142 | if( $allow[1] == "all" ) return true;
143 | $allow = array_map( 'trim', explode( ',', $allow[2] ) );
144 | if( in_array( trim( $optout ), $allow ) ) {
145 | return true;
146 | }
147 | return false;
148 | }
149 | return false;
150 | }
151 |
152 | /**
153 | * Outputs text if the given category is in the allowed types
154 | *
155 | * @param string $text Text to display
156 | * @param int $cat Category of text, such as PECHO_WARN, PECHO_NORMAL
157 | * @param string $func
158 | * @return void
159 | */
160 | function outputText( $text, $cat = 0, $func = 'echo' ) {
161 | global $pgVerbose;
162 |
163 | Hooks::runHook( 'OutputText', array( &$text, &$cat, &$func ) );
164 |
165 | if( in_array( $cat, $pgVerbose ) ) {
166 | if( $func == 'echo' ) {
167 | echo $text;
168 | } else {
169 | $func( $text );
170 | }
171 | }
172 | }
173 |
174 | /**
175 | * Shortcut for {@link outputText}
176 | *
177 | * @param string $text Text to display
178 | * @param int $cat Category of text, such as PECHO_WARN, PECHO_NORMAL
179 | * @param string $func
180 | * @link outputText
181 | * @return void
182 | */
183 | function pecho( $text, $cat = 0, $func = 'echo' ) {
184 | global $pgWebOutput;
185 | if( $pgWebOutput ) $text = str_replace( "\n", "
", $text );
186 | outputText( $text, $cat, $func );
187 | }
188 |
189 | /**
190 | * Echo function with color capabilities.
191 | *
192 | * Syntax:
193 | *
194 | * [Text to colorize|NAME] where NAME is the name of a defined style. For example:
195 | *
196 | * This text is standard terminal color. [This text will be yellow.|COMMENT] [This text will be white on red.|ERROR]
197 | *
198 | * Defined styles:
199 | *
200 | * - ERROR: White on red, bold
201 | * - INFO: Green text, bold
202 | * - PARAMETER: Cyan text
203 | * - COMMENT: Yellow text
204 | * - GREEN_BAR: White on green, bold
205 | * - RED_BAR: White on red, bold
206 | * - YELLOW_BAR: Black on yellow, bold
207 | * - INFO_BAR: Cyan text, bold
208 | *
209 | *
210 | * You can define your own styles by using this syntax:
211 | *
212 | * lime_colorizer::style('STYLE_NAME', array('bg' => 'red', 'fg' => 'white'));
213 | *
214 | * (Available colors: black, red, green, yellow, blue, magenta, cyan, white)
215 | *
216 | * You can also set options for how the text is formatted (not available on all systems):
217 | *
218 | * lime_colorizer::style('STYLE_NAME', array('bg' => 'red', 'fg' => 'white', 'bold' => true ));
(sets bold text)
219 | *
220 | * Available options: bold, underscore, blink, reverse, conceal
221 | *
222 | *
223 | * @access public
224 | * @param string $text
225 | * @param bool $return
226 | * @return string
227 | */
228 | function cecho( $text, $return = false ) {
229 | global $pgColorizer;
230 |
231 | if( !isset( $pgColorizer ) ) $pgColorizer = new lime_colorizer( true );
232 |
233 | $text = preg_replace_callback( '/\[(.+?)\|(\w+)\]/s', function ($m) {
234 | global $pgColorizer;
235 | return $pgColorizer->colorize($m[1], $m[2]);
236 | }, $text );
237 | if( $return ) return $text;
238 |
239 | echo $text;
240 |
241 | }
242 |
243 |
244 | /**
245 | * Generates a diff between two strings
246 | *
247 | * @package Text_Diff
248 | * @deprecated since 18 June 2013
249 | */
250 | function getTextDiff() {
251 | Peachy::deprecatedWarn( 'getTextDiff()', 'Diff::load()' );
252 | $args = func_get_args();
253 | return call_user_func_array( array( 'Diff', 'load' ), $args );
254 | }
255 |
256 | /**
257 | * Gets the first defined Wiki object
258 | *
259 | * @return Wiki|bool
260 | * @package initFunctions
261 | */
262 | function &getSiteObject() {
263 |
264 | foreach( $GLOBALS as $var ){
265 | if( is_object( $var ) ) {
266 | if( get_class( $var ) == "Wiki" ) {
267 | return $var;
268 | }
269 | }
270 | }
271 |
272 | return false;
273 | }
274 |
275 | /**
276 | * Returns an instance of the Page class as specified by $title or $pageid
277 | *
278 | * @param mixed $title Title of the page (default: null)
279 | * @param mixed $pageid ID of the page (default: null)
280 | * @param bool $followRedir Should it follow a redirect when retrieving the page (default: true)
281 | * @param bool $normalize Should the class automatically normalize the title (default: true)
282 | * @return Page
283 | * @package initFunctions
284 | */
285 | function &initPage( $title = null, $pageid = null, $followRedir = true, $normalize = true ) {
286 | $wiki = getSiteObject();
287 | if( !$wiki ) return false;
288 |
289 | $page = new Page( $wiki, $title, $pageid, $followRedir, $normalize );
290 | return $page;
291 | }
292 |
293 | /**
294 | * Returns an instance of the User class as specified by $pgUsername
295 | *
296 | * @param mixed $pgUsername Username
297 | * @return User|false
298 | * @package initFunctions
299 | */
300 | function &initUser( $pgUsername ) {
301 | $wiki = getSiteObject();
302 | if( !$wiki ) return false;
303 | return new User( $wiki, $pgUsername );
304 | }
305 |
306 | /**
307 | * Returns an instance of the Image class as specified by $filename or $pageid
308 | *
309 | * @param string $filename Filename
310 | * @return Image
311 | * @package initFunctions
312 | */
313 | function &initImage( $filename = null ) {
314 |
315 | $wiki = getSiteObject();
316 | if( !$wiki ) return false;
317 |
318 | $image = new Image( $wiki, $filename );
319 | return $image;
320 | }
321 |
322 | if( !function_exists( 'mb_strlen' ) ) {
323 |
324 | /**
325 | * Fallback implementation of mb_strlen.
326 | *
327 | * @link http://svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/includes/GlobalFunctions.php
328 | * @param string $str String to get
329 | * @return int
330 | * @package Fallback
331 | */
332 | function mb_strlen( $str ) {
333 | $counts = count_chars( $str );
334 | $total = 0;
335 |
336 | // Count ASCII bytes
337 | for( $i = 0; $i < 0x80; $i++ ){
338 | $total += $counts[$i];
339 | }
340 |
341 | // Count multibyte sequence heads
342 | for( $i = 0xc0; $i < 0xff; $i++ ){
343 | $total += $counts[$i];
344 | }
345 |
346 | return $total;
347 | }
348 | }
349 |
350 | if( !function_exists( 'mb_substr' ) ) {
351 | /**
352 | * Fallback implementation for mb_substr. This is VERY slow, from 5x to 100x slower. Use only if necessary.
353 | * @link http://svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/includes/GlobalFunctions.php
354 | * @package Fallback
355 | */
356 | function mb_substr( $str, $start, $count = 'end' ) {
357 | if( $start != 0 ) {
358 | $split = mb_substr_split_unicode( $str, intval( $start ) );
359 | $str = substr( $str, $split );
360 | }
361 |
362 | if( $count !== 'end' ) {
363 | $split = mb_substr_split_unicode( $str, intval( $count ) );
364 | $str = substr( $str, 0, $split );
365 | }
366 |
367 | return $str;
368 | }
369 |
370 | /**
371 | * Continuing support for mb_substr. Do not use.
372 | * @link http://svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/includes/GlobalFunctions.php
373 | * @package Fallback
374 | * @param integer $splitPos
375 | * @return int
376 | */
377 | function mb_substr_split_unicode( $str, $splitPos ) {
378 | if( $splitPos == 0 ) {
379 | return 0;
380 | }
381 |
382 | $byteLen = strlen( $str );
383 |
384 | if( $splitPos > 0 ) {
385 | if( $splitPos > 256 ) {
386 | // Optimize large string offsets by skipping ahead N bytes.
387 | // This will cut out most of our slow time on Latin-based text,
388 | // and 1/2 to 1/3 on East European and Asian scripts.
389 | $bytePos = $splitPos;
390 | while( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ){
391 | ++$bytePos;
392 | }
393 | $charPos = mb_strlen( substr( $str, 0, $bytePos ) );
394 | } else {
395 | $charPos = 0;
396 | $bytePos = 0;
397 | }
398 |
399 | while( $charPos++ < $splitPos ){
400 | ++$bytePos;
401 | // Move past any tail bytes
402 | while( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ){
403 | ++$bytePos;
404 | }
405 | }
406 | } else {
407 | $splitPosX = $splitPos + 1;
408 | $charPos = 0; // relative to end of string; we don't care about the actual char position here
409 | $bytePos = $byteLen;
410 | while( $bytePos > 0 && $charPos-- >= $splitPosX ){
411 | --$bytePos;
412 | // Move past any tail bytes
413 | while( $bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ){
414 | --$bytePos;
415 | }
416 | }
417 | }
418 |
419 | return $bytePos;
420 | }
421 | }
422 |
423 | if( !function_exists( 'iconv' ) ) {
424 | /**
425 | * Fallback iconv function.
426 | *
427 | * iconv support is not in the default configuration and so may not be present.
428 | * Assume will only ever use utf-8 and iso-8859-1.
429 | * This will *not* work in all circumstances.
430 | *
431 | * @access public
432 | * @param mixed $from
433 | * @param mixed $to
434 | * @param mixed $string
435 | * @return void
436 | * @package Fallback
437 | */
438 | function iconv( $from, $to, $string ) {
439 | if( substr( $to, -8 ) == '//IGNORE' ) $to = substr( $to, 0, strlen( $to ) - 8 );
440 | if( strcasecmp( $from, $to ) == 0 ) return $string;
441 | if( strcasecmp( $from, 'utf-8' ) == 0 ) return utf8_decode( $string );
442 | if( strcasecmp( $to, 'utf-8' ) == 0 ) return utf8_encode( $string );
443 | return $string;
444 | }
445 | }
446 |
447 | if( !function_exists( 'istainted' ) ) {
448 |
449 | /**
450 | * Fallback istainted function.
451 | *
452 | * @access public
453 | * @param mixed $var
454 | * @return integer
455 | * @package Fallback
456 | */
457 | function istainted( $var ) {
458 | return 0;
459 | }
460 |
461 | /**
462 | * Fallback taint function.
463 | *
464 | * @access public
465 | * @param mixed $var
466 | * @param int $level
467 | * @return void
468 | * @package Fallback
469 | */
470 | function taint( $var, $level = 0 ) { }
471 |
472 | /**
473 | * Fallback untaint function.
474 | *
475 | * @access public
476 | * @param mixed $var
477 | * @param int $level
478 | * @return void
479 | * @package Fallback
480 | */
481 | function untaint( $var, $level = 0 ) { }
482 |
483 | /**
484 | * @package Fallback
485 | */
486 | define( 'TC_HTML', 1 );
487 |
488 | /**
489 | * @package Fallback
490 | */
491 | define( 'TC_SHELL', 1 );
492 |
493 | /**
494 | * @package Fallback
495 | */
496 | define( 'TC_MYSQL', 1 );
497 |
498 | /**
499 | * @package Fallback
500 | */
501 | define( 'TC_PCRE', 1 );
502 |
503 | /**
504 | * @package Fallback
505 | */
506 | define( 'TC_SELF', 1 );
507 | }
508 |
--------------------------------------------------------------------------------
/HTTP.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * @file
22 | * HTTP object
23 | * Stores all cURL functions
24 | */
25 |
26 | /**
27 | * HTTP Class, stores cURL functions
28 | */
29 | class HTTP {
30 |
31 | /**
32 | * Curl object
33 | *
34 | * @var resource a cURL handle
35 | * @access private
36 | */
37 | private $curl_instance;
38 |
39 | /**
40 | * Hash to use for cookies
41 | *
42 | * @var string
43 | * @access private
44 | */
45 | private $cookie_hash;
46 |
47 | /**
48 | * Whether or not to enable GET:, POST:, and DLOAD: messages being sent to the terminal.
49 | *
50 | * @var bool
51 | * @access private
52 | */
53 | private $echo;
54 |
55 | /**
56 | * Useragent
57 | *
58 | * @var mixed
59 | * @access private
60 | */
61 | private $user_agent;
62 |
63 | /**
64 | * Temporary file where cookies are stored
65 | *
66 | * @var mixed
67 | * @access private
68 | */
69 | private $cookie_jar;
70 |
71 | /**
72 | * @var string|null
73 | */
74 | private $lastHeader = null;
75 |
76 | /**
77 | * Construction method for the HTTP class
78 | *
79 | * @access public
80 | *
81 | * @param bool $echo Whether or not to enable GET:, POST:, and DLOAD: messages being sent to the terminal. Default false;
82 | *
83 | * @note please consider using HTTP::getDefaultInstance() instead
84 | *
85 | * @throws RuntimeException
86 | * @throws DependencyError
87 | *
88 | * @return HTTP
89 | */
90 | public function __construct($echo = false)
91 | {
92 | if( !function_exists( 'curl_init' ) ) {
93 | throw new DependencyError( "cURL", "http://us2.php.net/manual/en/curl.requirements.php" );
94 | }
95 |
96 | $this->echo = $echo;
97 | $this->curl_instance = curl_init();
98 | if( $this->curl_instance === false ) {
99 | throw new RuntimeException( 'Failed to initialize curl' );
100 | }
101 | $this->cookie_hash = md5( time() . '-' . rand( 0, 999 ) );
102 | $this->cookie_jar = sys_get_temp_dir() . 'peachy.cookies.' . $this->cookie_hash . '.dat';
103 |
104 | $userAgent = 'Peachy MediaWiki Bot API';
105 | if( defined( 'PEACHYVERSION' ) ) $userAgent .= ' Version ' . PEACHYVERSION;
106 | $this->setUserAgent( $userAgent );
107 |
108 | Hooks::runHook( 'HTTPNewCURLInstance', array( &$this, &$echo ) );
109 |
110 | $this->setCookieJar( $this->cookie_jar );
111 |
112 | curl_setopt( $this->curl_instance, CURLOPT_MAXCONNECTS, 100 );
113 | curl_setopt( $this->curl_instance, CURLOPT_MAXREDIRS, 10 );
114 | $this->setCurlHeaders();
115 | curl_setopt( $this->curl_instance, CURLOPT_ENCODING, 'gzip' );
116 | curl_setopt( $this->curl_instance, CURLOPT_RETURNTRANSFER, 1 );
117 | curl_setopt( $this->curl_instance, CURLOPT_HEADER, 1 );
118 | curl_setopt( $this->curl_instance, CURLOPT_TIMEOUT, 10 );
119 | curl_setopt( $this->curl_instance, CURLOPT_CONNECTTIMEOUT, 10 );
120 |
121 | global $pgProxy;
122 | if( isset( $pgProxy ) && count( $pgProxy ) ) {
123 | curl_setopt( $this->curl_instance, CURLOPT_PROXY, $pgProxy['addr'] );
124 | if( isset( $pgProxy['type'] ) ) {
125 | curl_setopt( $this->curl_instance, CURLOPT_PROXYTYPE, $pgProxy['type'] );
126 | }
127 | if( isset( $pgProxy['userpass'] ) ) {
128 | curl_setopt( $this->curl_instance, CURLOPT_PROXYUSERPWD, $pgProxy['userpass'] );
129 | }
130 | if( isset( $pgProxy['port'] ) ) {
131 | curl_setopt( $this->curl_instance, CURLOPT_PROXYPORT, $pgProxy['port'] );
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * @param array $extraHeaders
138 | */
139 | private function setCurlHeaders( $extraHeaders = array() ) {
140 | curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array_merge( array( 'Expect:' ), $extraHeaders ) );
141 | }
142 |
143 | /**
144 | * @param boolean $verifyssl
145 | */
146 | private function setVerifySSL( $verifyssl = null ) {
147 | if( is_null( $verifyssl ) ) {
148 | global $verifyssl;
149 | }
150 | if( !$verifyssl ) {
151 | curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, false );
152 | curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 0 );
153 | } else {
154 | curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, true );
155 | //support for value of 1 will be removed in cURL 7.28.1
156 | curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 2 );
157 | }
158 | }
159 |
160 | /**
161 | * @param string $cookie_file
162 | */
163 | public function setCookieJar($cookie_file)
164 | {
165 | $this->cookie_jar = $cookie_file;
166 |
167 | Hooks::runHook( 'HTTPSetCookieJar', array( &$cookie_file ) );
168 |
169 | curl_setopt( $this->curl_instance, CURLOPT_COOKIEJAR, $cookie_file );
170 | curl_setopt( $this->curl_instance, CURLOPT_COOKIEFILE, $cookie_file );
171 | }
172 |
173 | /**
174 | * @param null $user_agent
175 | * @throws BadEntryError
176 | * @throws HookError
177 | */
178 | public function setUserAgent($user_agent = null)
179 | {
180 | $this->user_agent = $user_agent;
181 |
182 | Hooks::runHook( 'HTTPSetUserAgent', array( &$user_agent ) );
183 |
184 | curl_setopt( $this->curl_instance, CURLOPT_USERAGENT, $user_agent );
185 | }
186 |
187 | /**
188 | * @return string|bool Data. False on failure.
189 | * @throws CURLError
190 | */
191 | private function doCurlExecWithRetrys() {
192 | $data = false;
193 | for( $i = 0; $i <= 20; $i++ ){
194 | try{
195 | $response = curl_exec( $this->curl_instance );
196 | $header_size = curl_getinfo( $this->curl_instance, CURLINFO_HEADER_SIZE );
197 | $this->lastHeader = substr( $response, 0, $header_size );
198 | $data = substr( $response, $header_size );
199 | } catch( Exception $e ){
200 | if( curl_errno( $this->curl_instance ) != 0 ) {
201 | throw new CURLError( curl_errno( $this->curl_instance ), curl_error( $this->curl_instance ) );
202 | }
203 | if( $i == 20 ) {
204 | pecho( "Warning: A CURL error occurred. Attempted 20 times. Terminating attempts.", PECHO_WARN );
205 | return false;
206 | } else {
207 | pecho( "Warning: A CURL error occurred. Details can be found in the PHP error log. Retrying...", PECHO_WARN );
208 | }
209 | continue;
210 | }
211 | if( !is_null( $data ) && $data !== false ) {
212 | break;
213 | }
214 | }
215 | return $data;
216 | }
217 |
218 | /**
219 | * Get an url with HTTP GET
220 | *
221 | * @access public
222 | *
223 | * @param string $url URL to get
224 | * @param array|null $data Array of data to pass. Gets transformed into the URL inside the function. Default null.
225 | * @param array $headers Array of headers to pass to curl
226 | * @param bool $verifyssl override for the global verifyssl value
227 | *
228 | * @return bool|string Result
229 | */
230 | public function get( $url, $data = null, $headers = array(), $verifyssl = null ) {
231 | global $argv, $displayGetOutData;
232 |
233 | if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
234 | else $this->setCurlHeaders( $headers );
235 | $this->setVerifySSL( $verifyssl );
236 |
237 | curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 1 );
238 | curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
239 | curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
240 |
241 | /*if( !is_null( $this->use_cookie ) ) {
242 | curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
243 | }*/
244 |
245 | if( !is_null( $data ) && is_array( $data ) && !empty( $data ) ) {
246 | $url .= '?' . http_build_query( $data );
247 | }
248 |
249 | curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
250 |
251 | if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
252 | if( $displayGetOutData ) {
253 | pecho( "GET: $url\n", PECHO_NORMAL );
254 | }
255 | }
256 |
257 | Hooks::runHook( 'HTTPGet', array( &$this, &$url, &$data ) );
258 |
259 | return $this->doCurlExecWithRetrys();
260 |
261 | }
262 |
263 | /**
264 | * Returns the HTTP code of the last request
265 | *
266 | * @access public
267 | * @return int HTTP code
268 | */
269 | public function get_HTTP_code()
270 | {
271 | $ci = curl_getinfo( $this->curl_instance );
272 | return $ci['http_code'];
273 | }
274 |
275 | /**
276 | * Sends data via HTTP POST
277 | *
278 | * @access public
279 | *
280 | * @param string $url URL to send
281 | * @param array $data Array of data to pass.
282 | * @param array $headers Array of headers to pass to curl
283 | *
284 | * @param bool|null $verifyssl override for global verifyssl value
285 | *
286 | * @return bool|string Result
287 | */
288 | public function post($url, $data, $headers = array(), $verifyssl = null)
289 | {
290 | global $argv, $displayPostOutData;
291 |
292 | if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
293 | else $this->setCurlHeaders( $headers );
294 | $this->setVerifySSL( $verifyssl );
295 |
296 | curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 0 );
297 | curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 0 );
298 | curl_setopt( $this->curl_instance, CURLOPT_POST, 1 );
299 | curl_setopt( $this->curl_instance, CURLOPT_POSTFIELDS, $data );
300 |
301 | /*if( !is_null( $this->use_cookie ) ) {
302 | curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
303 | }*/
304 |
305 | curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
306 |
307 | if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
308 | if( $displayPostOutData ) {
309 | pecho( "POST: $url\n", PECHO_NORMAL );
310 | }
311 | }
312 |
313 | Hooks::runHook( 'HTTPPost', array( &$this, &$url, &$data ) );
314 |
315 | return $this->doCurlExecWithRetrys();
316 | }
317 |
318 | /**
319 | * Downloads an URL to the local disk
320 | *
321 | * @access public
322 | *
323 | * @param string $url URL to get
324 | * @param string $local Local filename to download to
325 | * @param array $headers Array of headers to pass to curl
326 | * @param bool|null $verifyssl
327 | *
328 | * @return bool
329 | */
330 | function download( $url, $local, $headers = array(), $verifyssl = null ) {
331 | global $argv;
332 |
333 | $out = fopen( $local, 'wb' );
334 |
335 | if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
336 | else $this->setCurlHeaders( $headers );
337 | $this->setVerifySSL( $verifyssl );
338 |
339 | // curl_setopt($this->curl_instance, CURLOPT_FILE, $out);
340 | curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
341 | curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
342 | curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
343 | curl_setopt( $this->curl_instance, CURLOPT_HEADER, 0 );
344 |
345 | if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
346 | pecho( "DLOAD: $url\n", PECHO_NORMAL );
347 | }
348 |
349 | Hooks::runHook( 'HTTPDownload', array( &$this, &$url, &$local ) );
350 |
351 | fwrite( $out, $this->doCurlExecWithRetrys() );
352 | fclose( $out );
353 |
354 | return true;
355 |
356 | }
357 |
358 | /**
359 | * Gets the Header for the last request made
360 | * @return null|string
361 | */
362 | public function getLastHeader() {
363 | return $this->lastHeader;
364 | }
365 |
366 | /**
367 | * Destructor, deletes cookies and closes cURL class
368 | *
369 | * @access public
370 | * @return void
371 | */
372 | public function __destruct()
373 | {
374 | Hooks::runHook( 'HTTPClose', array( &$this ) );
375 |
376 | curl_close( $this->curl_instance );
377 |
378 | //@unlink($this->cookie_jar);
379 | }
380 |
381 | /**
382 | * The below allows us to only have one instance of this class
383 | */
384 | private static $defaultInstance = null;
385 | private static $defaultInstanceWithEcho = null;
386 |
387 | /**
388 | * @param bool|false $echo
389 | * @return HTTP|null
390 | */
391 | public static function getDefaultInstance($echo = false)
392 | {
393 | if( $echo ) {
394 | if( is_null( self::$defaultInstanceWithEcho ) ) {
395 | self::$defaultInstanceWithEcho = new Http( $echo );
396 | }
397 | return self::$defaultInstanceWithEcho;
398 | } else {
399 | if( is_null( self::$defaultInstance ) ) {
400 | self::$defaultInstance = new Http( $echo );
401 | }
402 | return self::$defaultInstance;
403 | }
404 | }
405 |
406 | }
--------------------------------------------------------------------------------
/Includes/AutoUpdate.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * Checks for a new version of Peachy and installs it if there is one.
22 | */
23 | Class AutoUpdate {
24 |
25 | /**
26 | * @var Http
27 | */
28 | protected $http;
29 | protected $repository;
30 | protected $logfile;
31 | protected $lastused;
32 | protected $commits;
33 |
34 | function __construct( $http ) {
35 | global $pgIP, $pgExperimentalupdates;
36 | $this->http = $http;
37 | $this->repository = ( $pgExperimentalupdates ? 'master' : 'stable' );
38 | $this->logfile = ( $pgExperimentalupdates ? 'Update.log' : 'StableUpdate.log' );
39 | $this->lastused = ( file_exists( $pgIP . 'Includes/updateversion' ) ? unserialize( file_get_contents( $pgIP . 'Includes/updateversion' ) ) : 'Unknown' );
40 | }
41 |
42 | /**
43 | * Scans the GitHub repository for any updates and returns false if there are.
44 | *
45 | * @access public
46 | * @return bool
47 | */
48 | public function Checkforupdate() {
49 | global $pgIP, $pgExperimentalupdates;
50 | pecho( "Checking for updates...\n\n", PECHO_NORMAL );
51 | if( $pgExperimentalupdates ) pecho( "Warning: You have experimental updates switched on.\nExperimental updates are not fully tested and can cause problems,\nsuch as, bot misbehaviors up to complete crashes.\nUse at your own risk.\nPeachy will not revert back to a stable release until switched off.\n\n", PECHO_NOTICE );
52 | $data = json_decode( $this->http->get( 'https://api.github.com/repos/MW-Peachy/Peachy/branches/' . $this->repository, null, array(), false ), true );
53 | $this->commits = $data;
54 | /*if( strstr( $this->http->getLastHeader(), 'Status: 304 Not Modified') ) {
55 | pecho( "Peachy is up to date.\n\n", PECHO_NORMAL );
56 | return true;
57 | }*/
58 | if( is_array( $data ) && array_key_exists( 'message', $data ) && strpos( $data['message'], 'API rate limit exceeded' ) === 0 ) {
59 | pecho( "Cant check for updates right now, next window in " . $this->getTimeToNextLimitWindow() . "\n\n", PECHO_NOTICE );
60 | return true;
61 | }
62 | $this->cacheLastGithubETag();
63 | if( $this->lastused !== $this->repository ) {
64 | pecho( "Changing Peachy version to run using " . ( $pgExperimentalupdates ? "experimental" : "stable" ) . " updates.\n\n", PECHO_NOTICE );
65 | return false;
66 | }
67 | if( file_exists( $pgIP . 'Includes' . DIRECTORY_SEPARATOR . $this->logfile ) ) {
68 | $log = unserialize( file_get_contents( $pgIP . 'Includes' . DIRECTORY_SEPARATOR . $this->logfile ) );
69 | if( isset( $data['commit']['sha'] ) && $log['commit']['sha'] != $data['commit']['sha'] ) {
70 | pecho( "Update available!\n\n", PECHO_NOTICE );
71 | return false;
72 | } else {
73 | pecho( "Peachy is up to date.\n\n", PECHO_NORMAL );
74 | return true;
75 | }
76 | } else {
77 | pecho( "No update log found.\n\n", PECHO_WARN );
78 | return false;
79 | }
80 | }
81 |
82 | /**
83 | * Caches the last Etag from github in a tmp file
84 | */
85 | private function cacheLastGithubETag() {
86 | global $pgIP;
87 | if( preg_match( '/ETag\: \"([^\"]*)\"/', $this->http->getLastHeader(), $matches ) ) {
88 | file_put_contents( $pgIP . 'tmp' . DIRECTORY_SEPARATOR . 'github-ETag.tmp', $matches[1] );
89 | }
90 | }
91 |
92 | /**
93 | * @return string representing time to next github api request window
94 | */
95 | private function getTimeToNextLimitWindow() {
96 | if( preg_match( '/X-RateLimit-Reset: (\d*)/', $this->http->getLastHeader(), $matches ) ) {
97 | return gmdate( "i \m\i\\n\u\\t\\e\s s \s\\e\c\o\\n\d\s", $matches[1] - time() );
98 | }
99 | return 'Unknown';
100 | }
101 |
102 | private function getLocalPath( $fullUpdatePath ) {
103 | global $pgIP;
104 | $xplodesAt = DIRECTORY_SEPARATOR . 'gitUpdate' . DIRECTORY_SEPARATOR . 'Peachy-' . $this->repository . DIRECTORY_SEPARATOR;
105 | $parts = explode( $xplodesAt, $fullUpdatePath, 2 );
106 | return $pgIP . $parts[1];
107 | }
108 |
109 | /**
110 | * Updates the Peachy framework
111 | *
112 | * @access public
113 | * @return boolean|null
114 | */
115 | public function updatePeachy() {
116 | global $pgIP, $pgExperimentalupdates;
117 | $gitZip = $pgIP . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'gitUpdate.zip';
118 | if( file_exists( $gitZip ) ) {
119 | unlink( $gitZip );
120 | }
121 | file_put_contents( $gitZip, file_get_contents( 'http://github.com/MW-Peachy/Peachy/archive/' . $this->repository . '.zip' ) );
122 | $zip = new ZipArchive();
123 | $res = $zip->open( $gitZip );
124 | if( $res === true ) {
125 | $gitFolder = $pgIP . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'gitUpdate';
126 | if( file_exists( $gitFolder ) ) {
127 | $this->rrmdir( $gitFolder );
128 | }
129 | mkdir( $gitFolder, 02775 );
130 | $zip->extractTo( $gitFolder );
131 | $zip->close();
132 |
133 | $this->copyOverGitFiles( $gitFolder . DIRECTORY_SEPARATOR . 'Peachy-' . $this->repository );
134 |
135 | file_put_contents( $pgIP . 'Includes' . DIRECTORY_SEPARATOR . 'updateversion', serialize( ( $pgExperimentalupdates ? 'master' : 'stable' ) ) );
136 |
137 | pecho( "Peachy Updated! Changes will go into effect on the next run.\n\n", PECHO_NOTICE );
138 |
139 | file_put_contents( $pgIP . 'Includes' . DIRECTORY_SEPARATOR . $this->logfile, serialize( $this->commits ) );
140 | } else {
141 | pecho( "Update failed! Peachy could not retrieve all contents from GitHub.\n\n", PECHO_WARN );
142 | }
143 | }
144 |
145 | /**
146 | * @param string $gitFolder
147 | */
148 | private function copyOverGitFiles( $gitFolder ) {
149 | /** @var $fileInfo DirectoryIterator */
150 | foreach( new DirectoryIterator( $gitFolder ) as $fileInfo ){
151 | if( $fileInfo->isDot() ) continue;
152 | $gitPath = $fileInfo->getRealPath();
153 | $lclPatch = $this->getLocalPath( $gitPath );
154 |
155 | if( $fileInfo->isDir() ) {
156 | if( !file_exists( $lclPatch ) ) {
157 | mkdir( $lclPatch );
158 | }
159 | $this->copyOverGitFiles( $gitPath );
160 | } elseif( $fileInfo->isFile() ) {
161 | file_put_contents( $lclPatch, file_get_contents( $gitPath ) );
162 | }
163 | }
164 | }
165 |
166 | /**
167 | * recursively remove a directory
168 | * @param string $dir
169 | */
170 | private function rrmdir( $dir ) {
171 | if( is_dir( $dir ) ) {
172 | $objects = scandir( $dir );
173 | foreach( $objects as $object ){
174 | if( $object != "." && $object != ".." ) {
175 | if( filetype( $dir . "/" . $object ) == "dir" ) $this->rrmdir( $dir . "/" . $object ); else unlink( $dir . "/" . $object );
176 | }
177 | }
178 | reset( $objects );
179 | rmdir( $dir );
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/Includes/Autoloader.php:
--------------------------------------------------------------------------------
1 | 'Includes/Wiki.php',
14 | 'User' => 'Includes/User.php',
15 | 'Page' => 'Includes/Page.php',
16 | 'Image' => 'Includes/Image.php',
17 | 'XMLParse' => 'Includes/XMLParse.php',
18 | 'Script' => 'Script.php',
19 | 'UtfNormal' => 'Plugins/normalize/UtfNormal.php',
20 | 'DatabaseMySQL' => 'Plugins/database/MySQL.php',
21 | 'DatabaseMySQLi' => 'Plugins/database/MySQLi.php',
22 | 'DatabasePgSQL' => 'Plugins/database/PgSQL.php',
23 | 'DatabaseBase' => 'Plugins/database.php',
24 | 'ResultWrapper' => 'Plugins/database.php',
25 | 'lime_test' => 'Includes/lime_test.php',
26 | 'lime_output' => 'Includes/limeOutput.php',
27 | 'lime_output_color' => 'Includes/limeOutputColor.php',
28 | 'lime_colorizer' => 'Includes/limeColorizer.php',
29 | 'lime_harness' => 'Includes/limeHarness.php',
30 | 'lime_coverage' => 'Includes/limeCoverage.php',
31 | 'lime_registration' => 'Includes/limeRegistration.php',
32 | 'sfYaml' => 'Plugins/yaml/sfYaml.php',
33 | 'sfYamlDumper' => 'Plugins/yaml/sfYamlDumper.php',
34 | 'sfYamlInline' => 'Plugins/yaml/sfYamlInline.php',
35 | 'sfYamlParser' => 'Plugins/yaml/sfYamlParser.php',
36 | 'Text_Diff' => 'Plugins/diff/textdiff/Diff.php',
37 | 'Text_MappedDiff' => 'Plugins/diff/textdiff/Diff.php',
38 | 'Text_Diff_Op' => 'Plugins/diff/textdiff/Diff.php',
39 | 'Text_Diff_Op_copy' => 'Plugins/diff/textdiff/Diff.php',
40 | 'Text_Diff_Op_delete' => 'Plugins/diff/textdiff/Diff.php',
41 | 'Text_Diff_Op_add' => 'Plugins/diff/textdiff/Diff.php',
42 | 'Text_Diff_Op_change' => 'Plugins/diff/textdiff/Diff.php',
43 | 'Text_Diff3' => 'Plugins/diff/textdiff/Diff3.php',
44 | 'Text_Diff3_Op' => 'Plugins/diff/textdiff/Diff3.php',
45 | 'Text_Diff3_Op_copy' => 'Plugins/diff/textdiff/Diff3.php',
46 | 'Text_Diff3_BlockBuilder' => 'Plugins/diff/textdiff/Diff3.php',
47 | 'Text_Diff_ThreeWay' => 'Plugins/diff/textdiff/Diff/ThreeWay.php',
48 | 'Text_Diff_ThreeWay_Op' => 'Plugins/diff/textdiff/Diff/ThreeWay.php',
49 | 'Text_Diff_ThreeWay_Op_copy' => 'Plugins/diff/textdiff/Diff/ThreeWay.php',
50 | 'Text_Diff_ThreeWay_BlockBuilder' => 'Plugins/diff/textdiff/Diff/ThreeWay.php',
51 | 'Text_Diff_Renderer' => 'Plugins/diff/textdiff/Diff/Renderer.php',
52 | 'Text_Diff_Mapped' => 'Plugins/diff/textdiff/Diff/Mapped.php',
53 | 'Text_Diff_Renderer_unified' => 'Plugins/diff/textdiff/Diff/Renderer/unified.php',
54 | 'Text_Diff_Renderer_inline' => 'Plugins/diff/textdiff/Diff/Renderer/inline.php',
55 | 'Text_Diff_Renderer_context' => 'Plugins/diff/textdiff/Diff/Renderer/context.php',
56 | 'Text_Diff_Renderer_colorized' => 'Plugins/diff/textdiff/Diff/Renderer/colorized.php',
57 | 'Text_Diff_Renderer_dualview' => 'Plugins/diff/textdiff/Diff/Renderer/dualview.php',
58 | 'Text_Diff_Engine_xdiff' => 'Plugins/diff/textdiff/Diff/Engine/xdiff.php',
59 | 'Text_Diff_Engine_string' => 'Plugins/diff/textdiff/Diff/Engine/string.php',
60 | 'Text_Diff_Engine_shell' => 'Plugins/diff/textdiff/Diff/Engine/shell.php',
61 | 'Text_Diff_Engine_native' => 'Plugins/diff/textdiff/Diff/Engine/native.php',
62 | 'PeachyAWBFunctions' => 'Includes/PeachyAWBFunctions.php',
63 | );
64 |
65 | /**
66 | * Takes a class name and attempt to load it. Class name must be found in $pgAutoLoader.
67 | *
68 | * @param string $class_name Name of class we're looking for.
69 | * @return boolean|null Returns true if the function successfully loads the class. Otherwise returns false.
70 | * Returning false is important on failure as it allows Zend to try and look in other registered autoloaders as well.
71 | */
72 | public static function autoload($class_name)
73 | {
74 | global $pgIP;
75 |
76 | if (isset(self::$pgAutoLoader[$class_name]) && is_file($pgIP . self::$pgAutoLoader[$class_name])) {
77 | require_once($pgIP . self::$pgAutoLoader[$class_name]);
78 |
79 | return true;
80 | }
81 |
82 | if (is_file($pgIP . 'Plugins/' . $class_name . '.php')) {
83 | Hooks::runHook('LoadPlugin', array(&$class_name));
84 | require_once($pgIP . 'Plugins/' . $class_name . '.php');
85 |
86 | return true;
87 | }
88 | }
89 | }
90 |
91 | spl_autoload_register(array('AutoLoader', 'autoload'));
92 |
--------------------------------------------------------------------------------
/Includes/Exceptions.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * @file
22 | * Stores all the subclasses of Exception
23 | */
24 |
25 | /**
26 | * Generic Peachy Error
27 | *
28 | * @package Exceptions
29 | */
30 | class PeachyError extends Exception {
31 |
32 | public function __construct( $code, $text ) {
33 | parent::__construct(
34 | "API Error: " . $code . " (" . $text . ")"
35 | );
36 | }
37 | }
38 |
39 | /**
40 | * Assertation error bot
41 | *
42 | * @package Exceptions
43 | */
44 | class AssertFailure extends Exception {
45 |
46 | public function __contstruct( $type ) {
47 | parent::__construct(
48 | "Assert Failure: ".( $type == "bot" ? "Bot is no longer flagged as a bot" : "User is logged out" )
49 | );
50 | }
51 | }
52 |
53 | /**
54 | * Generic API Error
55 | *
56 | * @package Exceptions
57 | */
58 | class MWAPIError extends Exception {
59 |
60 | public function __construct( $error ) {
61 | parent::__construct(
62 | "API Error: " . $error['code'] . " (" . $error['text'] . $error['info'] . ")"
63 | );
64 | }
65 | }
66 |
67 | /**
68 | * Error with user permissions
69 | *
70 | * @package Exceptions
71 | */
72 | class PermissionsError extends Exception {
73 | public function __construct( $error ) {
74 | parent::__construct(
75 | "Permissions Error: " . $error
76 | );
77 | }
78 | }
79 |
80 | /**
81 | * Generic cURL Error
82 | *
83 | * @package Exceptions
84 | */
85 | class CURLError extends Exception {
86 | private $errno;
87 | private $error;
88 |
89 | /**
90 | * @param integer $errno
91 | * @param string $error
92 | */
93 | public function __construct( $errno, $error ) {
94 | $this->errno = $errno;
95 | $this->error = $error;
96 |
97 | parent::__construct(
98 | "cURL Error (" . $this->errno . "): " . $this->error
99 | );
100 | }
101 |
102 | public function get_errno() {
103 | return $this->errno;
104 | }
105 |
106 | public function get_error() {
107 | return $this->error;
108 | }
109 |
110 | }
111 |
112 | /**
113 | * Invalid Title Error
114 | *
115 | * @package Exceptions
116 | */
117 | class BadTitle extends Exception {
118 |
119 | private $title;
120 |
121 | public function __construct( $title ) {
122 | $this->title = $title;
123 | parent::__construct(
124 | "Invalid title: $title"
125 | );
126 |
127 | }
128 |
129 | public function getTitle() {
130 | return $this->title;
131 | }
132 | }
133 |
134 | /**
135 | * No Title Error
136 | *
137 | * @package Exceptions
138 | */
139 | class NoTitle extends Exception {
140 |
141 | public function __construct() {
142 | parent::__construct(
143 | "No title or pageid stated when instantiating Page class"
144 | );
145 |
146 | }
147 | }
148 |
149 | /**
150 | * No User Error
151 | *
152 | * @package Exceptions
153 | */
154 | class NoUser extends Exception {
155 |
156 | public function __construct( $title ) {
157 | parent::__construct(
158 | "Non-existant user: $title"
159 | );
160 |
161 | }
162 | }
163 |
164 | /**
165 | * Blocked User Error
166 | *
167 | * @package Exceptions
168 | */
169 | class UserBlocked extends Exception {
170 |
171 | public function __construct( $pgUsername = "User" ) {
172 | parent::__construct(
173 | $pgUsername . " is currently blocked."
174 | );
175 |
176 | }
177 |
178 | }
179 |
180 | /**
181 | * Logged Out Error
182 | *
183 | * @package Exceptions
184 | */
185 | class LoggedOut extends Exception {
186 |
187 | public function __construct() {
188 | parent::__construct(
189 | "User is not logged in."
190 | );
191 |
192 | }
193 |
194 | }
195 |
196 | /**
197 | * Missing DependencyError Error
198 | *
199 | * @package Exceptions
200 | */
201 | class DependencyError extends Exception {
202 |
203 | public function __construct( $software, $url = false ) {
204 | $message = "Missing dependency: \`" . $software . "\`. ";
205 | if( $url ) $message .= "Download from <$url>";
206 | parent::__construct(
207 | $message
208 | );
209 |
210 | }
211 |
212 | }
213 |
214 | /**
215 | * Misspelling of "dependency", used for backwards compatibility
216 | *
217 | * @package Exceptions
218 | * @deprecated since 31 Jan 2014
219 | */
220 | class DependancyError extends DependencyError {
221 | public function __construct( $software, $url = false ) {
222 | parent::__construct( $software, $url );
223 | }
224 | }
225 |
226 | /**
227 | * Login Error
228 | *
229 | * @package Exceptions
230 | */
231 | class LoginError extends Exception {
232 |
233 | /**
234 | * @param string[] $error
235 | */
236 | public function __construct( $error ) {
237 | parent::__construct(
238 | "Login Error: " . $error[0] . " (" . $error[1] . ")"
239 | );
240 | }
241 | }
242 |
243 | /**
244 | * Peachy Hook Error
245 | *
246 | * @package Exceptions
247 | * @package Peachy_Hooks
248 | */
249 | class HookError extends Exception {
250 | public function __construct( $error ) {
251 | parent::__construct(
252 | "Hook Error: " . $error
253 | );
254 | }
255 | }
256 |
257 | /**
258 | * Generic Database Error
259 | *
260 | * @package Exceptions
261 | * @package Peachy_Database
262 | */
263 | class DBError extends Exception {
264 |
265 | /**
266 | * @param string $sql
267 | */
268 | public function __construct( $error, $errno, $sql = null ) {
269 | parent::__construct(
270 | "Database Error: " . $error . " (code $errno) " . $sql
271 | );
272 | }
273 | }
274 |
275 | /**
276 | * Generic Edit Error
277 | *
278 | * @package Exceptions
279 | */
280 | class EditError extends Exception {
281 |
282 | /**
283 | * @param string $error
284 | * @param string $text
285 | */
286 | public function __construct( $error, $text ) {
287 | parent::__construct(
288 | "Edit Error: " . $error . " ($text)"
289 | );
290 | }
291 | }
292 |
293 | /**
294 | * Generic Move Error
295 | *
296 | * @package Exceptions
297 | */
298 | class MoveError extends Exception {
299 | public function __construct( $error, $text ) {
300 | parent::__construct(
301 | "Move Error: " . $error . " ($text)"
302 | );
303 | }
304 | }
305 |
306 | /**
307 | * Generic Delete Error
308 | *
309 | * @package Exceptions
310 | */
311 | class DeleteError extends Exception {
312 | public function __construct( $error, $text ) {
313 | parent::__construct(
314 | "Delete Error: " . $error . " ($text)"
315 | );
316 | }
317 | }
318 |
319 | /**
320 | * Generic Undelete Error
321 | *
322 | * @package Exceptions
323 | */
324 | class UndeleteError extends Exception {
325 | public function __construct( $error, $text ) {
326 | parent::__construct(
327 | "Undelete Error: " . $error . " ($text)"
328 | );
329 | }
330 | }
331 |
332 | /**
333 | * Generic Protect Error
334 | *
335 | * @package Exceptions
336 | */
337 | class ProtectError extends Exception {
338 | public function __construct( $error, $text ) {
339 | parent::__construct(
340 | "Protect Error: " . $error . " ($text)"
341 | );
342 | }
343 | }
344 |
345 | /**
346 | * Generic Email Error
347 | *
348 | * @package Exceptions
349 | */
350 | class EmailError extends Exception {
351 | public function __construct( $error, $text ) {
352 | parent::__construct(
353 | "Email Error: " . $error . " ($text)"
354 | );
355 | }
356 | }
357 |
358 | /**
359 | * Generic Image Error
360 | *
361 | * @package Exceptions
362 | */
363 | class ImageError extends Exception {
364 | public function __construct( $error ) {
365 | parent::__construct(
366 | "Image Error: " . $error
367 | );
368 | }
369 | }
370 |
371 | /**
372 | * Error for wrong parameters in a function
373 | *
374 | * @package Exceptions
375 | */
376 | class BadEntryError extends Exception {
377 |
378 | /**
379 | * @param string $error
380 | */
381 | public function __construct( $error, $text ) {
382 | parent::__construct(
383 | "Bad Entry Error: " . $error . " ($text)"
384 | );
385 | }
386 | }
387 |
388 | /**
389 | * Generic XML Error
390 | *
391 | * @package Exceptions
392 | * @package XML
393 | */
394 | class XMLError extends Exception {
395 | public function __construct( $error ) {
396 | parent::__construct(
397 | "XML Error: " . $error
398 | );
399 | }
400 | }
401 |
402 |
--------------------------------------------------------------------------------
/Includes/Hooks.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * @file
22 | * Hooks object
23 | * Stores the runHook function, which runs all hook functions
24 | */
25 |
26 | /**
27 | * Hooks class
28 | * Stores and runs {@link http://wiki.peachy.compwhizii.net/wiki/Manual/Hooks hooks}
29 | *
30 | */
31 | class Hooks {
32 |
33 | /**
34 | * Search for hook functions and run them if defined
35 | *
36 | * @param string $hook_name Name of hook to search for
37 | * @param array $args Arguments to pass to the hook function
38 | * @throws HookError
39 | * @throws BadEntryError
40 | * @return mixed Output of hook function
41 | */
42 | public static function runHook( $hook_name, $args = array() ) {
43 | global $pgHooks;
44 |
45 | if( !isset( $pgHooks[$hook_name] ) ) return null;
46 |
47 | if( !is_array( $pgHooks[$hook_name] ) ) {
48 | throw new HookError( "Hook assignment for event `$hook_name` is not an array. Syntax is " . '$pgHooks[\'hookname\'][] = "hook_function";' );
49 | }
50 |
51 | $method = null;
52 | foreach( $pgHooks[$hook_name] as $function ){
53 | if( is_array( $function ) ) {
54 | if( count( $function ) < 2 ) {
55 | throw new HookError( "Not enough parameters in array specified for `$hook_name` hook" );
56 | } elseif( is_object( $function[0] ) ) {
57 | $object = $function[0];
58 | $method = $function[1];
59 | if( count( $function ) > 2 ) {
60 | $data = $function[2];
61 | }
62 | } elseif( is_string( $function[0] ) ) {
63 | $method = $function[0];
64 | if( count( $function ) > 1 ) {
65 | $data = $function[1];
66 | }
67 | }
68 |
69 | } elseif( is_string( $function ) ) {
70 | $method = $function;
71 | }
72 |
73 | if( isset( $data ) ) {
74 | $args = array_merge( array( $data ), $args );
75 | }
76 |
77 | if( isset( $object ) ) {
78 | $fncarr = array( $object, $method );
79 | } elseif( is_string( $method ) && in_string( "::", $method ) ) {
80 | $fncarr = explode( "::", $method );
81 | } else {
82 | $fncarr = $method;
83 | }
84 |
85 | //is_callable( $fncarr ); //Apparently this is a bug. Thanks, MW!
86 |
87 | if( !is_callable( $fncarr ) ) {
88 | throw new BadEntryError( "MissingFunction", "Hook function $fncarr was not defined" );
89 | }
90 |
91 | $hookRet = call_user_func_array( $fncarr, $args );
92 |
93 | if( !is_null( $hookRet ) ) return $hookRet;
94 |
95 | }
96 |
97 | }
98 |
99 | }
--------------------------------------------------------------------------------
/Includes/Peachy.php:
--------------------------------------------------------------------------------
1 | Checkforupdate();
27 | if( !$Uptodate ) $updater->updatePeachy();*/
28 |
29 | if( !is_null( $config_name ) ) {
30 | $config_params = self::parse_config( $config_name );
31 |
32 | } else {
33 | $config_params = array(
34 | 'username' => $pgUsername,
35 | 'password' => $password,
36 | 'baseurl' => $base_url,
37 | 'method' => 'legacy'
38 | );
39 |
40 | }
41 |
42 | if( is_null( $config_params['baseurl'] ) || !isset( $config_params['baseurl'] ) ) {
43 | throw new LoginError( array( "MissingParam", "The baseurl parameter was not set." ) );
44 | }
45 |
46 | if( !isset( $config_params['username'] ) ) {
47 | $config_params['nologin'] = true;
48 | }
49 | if( !isset( $config_params['password'] ) && isset( $config_params['method'] ) && $config_params['method'] == "legacy" ) {
50 | $config_params['nologin'] = true;
51 | }
52 |
53 | list( $version, $extensions ) = self::wikiChecks( $config_params['baseurl'] );
54 |
55 | Hooks::runHook( 'StartLogin', array( &$config_params, &$extensions ) );
56 |
57 | $w = new $classname( $config_params, $extensions, false, null );
58 | $w->mwversion = $version;
59 |
60 | return $w;
61 | }
62 |
63 | /**
64 | * Performs various checks and settings
65 | * Checks if MW version is at least {@link MINMW}
66 | *
67 | * @static
68 | * @access public
69 | * @param string $base_url URL to api.php
70 | * @throws DependencyError|string
71 | * @return array Installed extensions
72 | */
73 | public static function wikiChecks( $base_url ) {
74 | $http = HTTP::getDefaultInstance();
75 |
76 | $siteInfo = unserialize(
77 | $http->get(
78 | $base_url,
79 | array(
80 | 'action' => 'query',
81 | 'meta' => 'siteinfo',
82 | 'format' => 'php',
83 | 'siprop' => 'extensions|general',
84 | )
85 | )
86 | );
87 |
88 | if (isset($siteInfo['error']) && $siteInfo['error']['code'] == 'readapidenied') {
89 | global $pgHooks;
90 | $pgHooks['PostLogin'][] = array( 'Peachy::wikiChecks', $base_url );
91 | return array( MINMW, array() );
92 | }
93 |
94 | $version = preg_replace('/[^0-9\.]/', '', $siteInfo['query']['general']['generator']);
95 |
96 | if( version_compare( $version, MINMW ) < 0 ) {
97 | throw new DependencyError( "MediaWiki " . MINMW, "http://mediawiki.org" );
98 | }
99 |
100 | $extensions = array();
101 |
102 | foreach ($siteInfo['query']['extensions'] as $ext) {
103 | if( isset( $ext['version'] ) ) {
104 | $extensions[$ext['name']] = $ext['version'];
105 | } else {
106 | $extensions[$ext['name']] = '';
107 | }
108 | }
109 |
110 | return array( $version, $extensions );
111 | }
112 |
113 | /**
114 | * Checks for config files, parses them.
115 | *
116 | * @access private
117 | * @static
118 | * @param string $config_name Name of config file
119 | * @throws BadEntryError
120 | * @return array Config params
121 | */
122 | private static function parse_config( $config_name ) {
123 | global $pgIP;
124 | if( !is_file( $config_name ) ) {
125 | if( !is_file( $pgIP . 'Configs/' . $config_name . '.cfg' ) ) {
126 | throw new BadEntryError( "BadConfig", "A non-existent configuration file was specified." );
127 | } else {
128 | $config_name = $pgIP . 'Configs/' . $config_name . '.cfg';
129 | }
130 | }
131 |
132 | $config_params = parse_ini_file( $config_name );
133 |
134 | if( isset( $config_params['useconfig'] ) ) {
135 | $config_params = $config_params + self::parse_config( $config_params['useconfig'] );
136 | }
137 |
138 | return $config_params;
139 | }
140 |
141 | /**
142 | * Function that displays an error message when an End-User attempts to use a function no longer included in Peachy
143 | *
144 | * @param null|string $method
145 | * @param null|string $newFunction
146 | * @param string $message
147 | */
148 | public static function deprecatedWarn($method, $newFunction, $message = null)
149 | {
150 | if( is_null( $message ) ) {
151 | $message = "Warning: $method is deprecated. Please use $newFunction instead.";
152 | }
153 |
154 | $message = "[$message|YELLOW_BAR]\n\n";
155 |
156 | pecho( $message, PECHO_WARN, 'cecho' );
157 | }
158 |
159 | /**
160 | * Checks for and returns an SVN repository version.
161 | *
162 | * @return array|bool
163 | */
164 | public static function getSvnInfo() {
165 | global $pgIP;
166 |
167 | // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
168 | $entries = $pgIP . '/.svn/entries';
169 |
170 | if( !file_exists( $entries ) ) {
171 | return false;
172 | }
173 |
174 | $lines = file( $entries );
175 | if( !count( $lines ) ) {
176 | return false;
177 | }
178 |
179 | // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
180 | if( preg_match( '/^<\?xml/', $lines[0] ) ) {
181 | return false;
182 | }
183 |
184 | // Subversion is release 1.4 or above.
185 | if( count( $lines ) < 11 ) {
186 | return false;
187 | }
188 |
189 | $info = array(
190 | 'checkout-rev' => intval( trim( $lines[3] ) ),
191 | 'url' => trim( $lines[4] ),
192 | 'repo-url' => trim( $lines[5] ),
193 | 'directory-rev' => intval( trim( $lines[10] ) )
194 | );
195 |
196 | return $info;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Includes/PeachyAWBFunctions.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | /**
21 | * PeachyAWBFunctions class.
22 | *
23 | * It consists of various static functions used for the PeachyAWB script
24 | * Much of the code is derived from Pywikipedia and AWB, both under the GPL
25 | *
26 | */
27 | class PeachyAWBFunctions {
28 |
29 | public static $html_tags = array(
30 | # Tags that must be closed
31 | 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
32 | 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
33 | 'strike', 'strong', 'tt', 'var', 'div', 'center',
34 | 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
35 | 'ruby', 'rt', 'rb', 'rp', 'p', 'span', 'u', 'abbr',
36 | # Single
37 | 'br', 'hr', 'li', 'dt', 'dd',
38 | # Elements that cannot have close tags
39 | 'br', 'hr',
40 | # Tags that can be nested--??
41 | 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
42 | 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span',
43 | # Can only appear inside table, we will close them
44 | 'td', 'th', 'tr',
45 | # Tags used by list
46 | 'ul', 'ol',
47 | # Tags that can appear in a list
48 | 'li',
49 | ## pairs
50 | # "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
51 | # "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s", "span",
52 | # "strike", "strong", "tt", "var", "div", "center",
53 | # "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
54 | # "ruby", "rt" , "rb" , "rp",
55 | ## single
56 | # "br", "p", "hr", "li", "dt", "dd",
57 | ## nest
58 | # "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
59 | # "dl", "font", "big", "small", "sub", "sup",
60 | ## table tags
61 | # "td", "th", "tr",
62 |
63 | );
64 |
65 | public static $html_attrs = array(
66 | "title", "align", "lang", "dir", "width", "height",
67 | "bgcolor", "clear", "noshade",
68 | "cite", "size", "face", "color",
69 | "type", "start", "value", "compact",
70 | #/* For various lists, mostly deprecated but safe */
71 | "summary", "width", "border", "frame", "rules",
72 | "cellspacing", "cellpadding", "valign", "char",
73 | "charoff", "colgroup", "col", "span", "abbr", "axis",
74 | "headers", "scope", "rowspan", "colspan",
75 | "id", "class", "name", "style"
76 | );
77 |
78 | public static $html_colors = array(
79 | '#F0FFFF' => 'azure', '#F5F5DC' => 'beige', '#FFE4C4' => 'bisque', '#000000' => 'black', '#0000FF' => 'blue',
80 | '#A52A2A' => 'brown', '#FF7F50' => 'coral', '#FFF8DC' => 'cornsilk', '#DC143C' => 'crimson',
81 | '#00FFFF' => 'cyan', '#00008B' => 'darkBlue', '#008B8B' => 'darkCyan', '#A9A9A9' => 'darkGray',
82 | '#8B0000' => 'darkRed', '#FF1493' => 'deepPink', '#696969' => 'dimGray', '#FF00FF' => 'fuchsia',
83 | '#FFD700' => 'gold', '#808080' => 'gray', '#008000' => 'green', '#F0FFF0' => 'honeyDew', '#FF69B4' => 'hotPink',
84 | '#4B0082' => 'indigo', '#FFFFF0' => 'ivory', '#F0E68C' => 'khaki', '#E6E6FA' => 'lavender', '#00FF00' => 'lime',
85 | '#FAF0E6' => 'linen', '#800000' => 'maroon', '#FFE4B5' => 'moccasin', '#000080' => 'navy',
86 | '#FDF5E6' => 'oldLace', '#808000' => 'olive', '#FFA500' => 'orange', '#DA70D6' => 'orchid', '#CD853F' => 'peru',
87 | '#FFC0CB' => 'pink', '#DDA0DD' => 'plum', '#800080' => 'purple', '#FF0000' => 'red', '#FA8072' => 'salmon',
88 | '#2E8B57' => 'seaGreen', '#FFF5EE' => 'seaShell', '#A0522D' => 'sienna', '#C0C0C0' => 'silver',
89 | '#87CEEB' => 'skyBlue', '#FFFAFA' => 'snow', '#D2B48C' => 'tan', '#008080' => 'teal', '#D8BFD8' => 'thistle',
90 | '#FF6347' => 'tomato', '#EE82EE' => 'violet', '#F5DEB3' => 'wheat', '#FFFFFF' => 'white', '#FFFF00' => 'yellow',
91 | );
92 |
93 | public static $stub_search = '[Ss]tub';
94 |
95 | public static $interwiki_map = array();
96 |
97 | public static $typo_list = array();
98 |
99 | public static function fixVars( Wiki $wiki ) {
100 | $interwiki = $wiki->siteinfo( array( 'interwikimap' ) );
101 | self::$interwiki_map = $interwiki['query']['interwikimap'];
102 | }
103 |
104 | public static function fixCitations( $text ) {
105 |
106 | //merge all variant of cite web
107 | $text = preg_replace( '/\{\{\s*(cite[_ \-]*(url|web|website)|Web[_ \-]*(citation|cite|reference|reference[_ ]4))(?=\s*\|)/i', '{{cite web', $text );
108 |
109 | //Remove formatting on certian parameters
110 | $text = preg_replace( "/(\|\s*(?:agency|author|first|format|language|last|location|month|publisher|work|year)\s*=\s*)(''|'''|''''')((?:\[\[[^][|]+|\[\[|)[][\w\s,.~!`\"]+)(''+)(?=\s*\|[\w\s]+=|\s*\}\})/", '$1$3', $text );
111 |
112 | //Unlink PDF in format parameters
113 | $text = preg_replace( '/(\|\s*format\s*=\s*)\[\[(adobe|portable|document|file|format|pdf|\.|\s|\(|\)|\|)+\]\]/i', '$1PDF', $text );
114 | $text = preg_replace( '/(\|\s*format\s*=\s*)(\s*\.?(adobe|portable|document|file|format|pdf|\(|\)))+?(\s*[|}])/i', '$1PDF$4', $text );
115 |
116 | //No |format=HTML says {{cite web/doc}}
117 | $text = preg_replace( '/(\{\{cite[^{}]+)\|\s*format\s*=\s*(\[\[[^][|]+\||\[\[|)(\]\]| |html?|world|wide|web)+\s*(?=\||\}\})/i', '$1', $text );
118 |
119 | //Fix accessdate tags [[WP:AWB/FR#Fix accessdate tags]]
120 | $text = preg_replace(
121 | array(
122 | '/(\|\s*)a[ces]{3,8}date(\s*=\s*)(?=[^{|}]*20\d\d|\}\})/',
123 | '/accessdate(\s*=\s*)\[*(200\d)[/_\-](\d{2})[/_\-](\d{2})\]*/',
124 | '/(\|\s*)a[cs]*es*mou*nthday(\s*=\s*)/',
125 | '/(\|\s*)a[cs]*es*daymou*nth(\s*=\s*)/',
126 | '/(\|\s*)accessdate(\s*=\s*[0-3]?[0-9] +(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*)([^][<>}{]*accessyear[\s=]+20\d\d)/',
127 | '/(\|\s*)accessdate(\s*=\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w* +[0-3]?[0-9])([^][<>}{]*accessyear[\s=]+20\d\d)/',
128 | '/(\|\s*)accessdaymonth(\s*=\s*)\s*([^{|}<>]+?)\s*(\|[^][<>}{]*accessyear[\s=]+)(20\d\d)/',
129 | '/(\|\s*)accessmonthday(\s*=\s*)\s*([^{|}<>]+?)\s*(\|[^][<>}{]*accessyear[\s=]+)(20\d\d)/',
130 | ),
131 | array(
132 | '$1accessdate$2',
133 | 'accessdate$1$2-$3-$4',
134 | '$1accessmonthday$2',
135 | '$1accessdaymonth$2',
136 | '$1accessdaymonth$2$3',
137 | '$1accessmonthday$2$3',
138 | '$1accessdate$2$3 $5',
139 | '$1accessdate$2$3, $5',
140 | ),
141 | $text
142 | );
143 |
144 | //Fix improper dates
145 | $text = preg_replace(
146 | array(
147 | '/(\{\{cit[ea][^{}]+\|\s*date\s*=\s*\d{2}[/\-.]\d{2}[/\-.])([5-9]\d)(?=\s*[|}])/i',
148 | '/(\{\{cit[ea][^{}]+\|\s*date\s*=\s*)(0[1-9]|1[012])[/\-.](1[3-9]|2\d|3[01])[/\-.](19\d\d|20\d\d)(?=\s*[|}])/i',
149 | '/(\{\{cit[ea][^{}]+\|\s*date\s*=\s*)(1[3-9]|2\d|3[01])[/\-.](0[1-9]|1[012])[/\-.](19\d\d|20\d\d)(?=\s*[|}])/i',
150 | ),
151 | array(
152 | '${1}19$2',
153 | '$1$4-$2-$3',
154 | '$1$4-$3-$2',
155 | ),
156 |
157 | $text
158 | );
159 |
160 | //Fix URLS lacking http://
161 | $text = preg_replace( '/(\|\s*url\s*=\s*)([0-9a-z.\-]+\.[a-z]{2,4}/[^][{|}:\s"]\s*[|}])/', '$1http://$2', $text );
162 |
163 | //Fix {{citation|title=[url title]}}
164 | $text = preg_replace( '/(\{\{cit[ea][^{}]*?)(\s*\|\s*)(?:url|title)(\s*=\s*)\[([^][<>\s"]*) +([^]\n]+)\](?=[|}])/i', '$1$2url$3$4$2title$3$5', $text );
165 |
166 | return $text;
167 |
168 | }
169 |
170 | public static function fixDateTags( $text ) {
171 |
172 | $text = preg_replace( '/\{\{\s*(?:template:)?\s*(?:wikify(?:-date)?|wfy|wiki)(\s*\|\s*section)?\s*\}\}/iS', "{{Wikify$1|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
173 | $text = preg_replace( '/\{\{(template:)?(Clean( ?up)?|CU|Tidy)\}\}/iS', "{{Cleanup|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
174 | $text = preg_replace( '/\{\{(template:)?(Linkless|Orphan)\}\}/iS', "{{Orphan|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
175 | $text = preg_replace( '/\{\{(template:)?(Unreferenced(sect)?|add references|cite[ -]sources?|cleanup-sources?|needs? references|no sources|no references?|not referenced|references|unref|unsourced)\}\}/iS', "{{Unreferenced|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
176 | $text = preg_replace( '/\{\{(template:)?(Uncategori[sz]ed|Uncat|Classify|Category needed|Catneeded|categori[zs]e|nocats?)\}\}/iS', "{{Uncategorized|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
177 | $text = preg_replace( '/\{\{(template:)?(Trivia2?|Too ?much ?trivia|Trivia section|Cleanup-trivia)\}\}/iS', "{{Trivia|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
178 | $text = preg_replace( '/\{\{(template:)?(deadend|DEP)\}\}/iS', "{{Deadend|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
179 | $text = preg_replace( '/\{\{(template:)?(copyedit|g(rammar )?check|copy-edit|cleanup-copyedit|cleanup-english)\}\}/iS', "{{Copyedit|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
180 | $text = preg_replace( '/\{\{(template:)?(sources|refimprove|not verified)\}\}/iS', "{{Refimprove|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
181 | $text = preg_replace( '/\{\{(template:)?(Expand)\}\}/iS', "{{Expand|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}", $text );
182 | //$text = preg_replace( '/\{\{(?:\s*[Tt]emplate:)?(\s*(?:[Cc]n|[Ff]act|[Pp]roveit|[Cc]iteneeded|[Uu]ncited|[Cc]itation needed)\s*(?:\|[^{}]+(?\]*>))+\w+[^\w\s<>]*)
(?=\n[*#:;]|\n?]*>)\n?/mi',
214 | '/<[/]?br([^{/}<>]*?/?)>/mi',
215 | '/
/i',
216 | '/
/',
217 | ),
218 | array(
219 | '$1',
220 | '
',
221 | '{{-}}',
222 | '{{clear$1}}'
223 | ),
224 | $text
225 | );
226 |
227 | $text = preg_replace( '/(]*)> *\n?]*>)((?:[^<]|<(?!/?font))*? *\n?)/mi', '$1$2$3', $text );
228 |
229 | $text = preg_replace( '/]*)>\[\[([^[\]{|}]+)\|([^[\]\n]*?)\]\]/mi', '[[$2|$3]]', $text );
230 |
231 | $text = preg_replace( '/(?!\[\[)((?:[^<]|<(?!/?font))*?)(?/mi', '$3', $text );
232 |
233 | return $text;
234 |
235 | }
236 |
237 | public static function fixHyperlinking( $text ) {
238 |
239 | $text = preg_replace( '/(http:\/\/[^][<>\s"|])(&client=firefox-a|<=)(?=[][<>\s"|&])/', '$1', $text );
240 |
241 | $text = str_replace( '[{{SERVER}}{{localurl:', '[{{fullurl:', $text );
242 |
243 | $text = preg_replace( '/[(](?:see|) *(http:\/\/[^][<>"\s(|)]+[\w=\/&])\s?[)]/i', '<$1>', $text );
244 |
245 | $text = preg_replace( '/\[\[(https?:\/\/[^\]\n]+?)\]\]/', '[$1]', $text );
246 | $text = preg_replace( '/\[\[(https?:\/\/.+?)\]/', '[$1]', $text );
247 |
248 | $text = preg_replace( '/\[\[(:?)Image:([^][{|}]+\.(pdf|midi?|ogg|ogv|xcf))(?=\||\]\])/i', '[[$1File:$2', $text );
249 |
250 | $text = preg_replace(
251 | array(
252 | '/(http:\/* *){2,}(?=[a-z0-9:.\-]+\/)/i',
253 | "/(\[\w+:\/\/[^][<>\"\s]+?)''/i",
254 | '/\[\n*(\w+:\/\/[^][<>"\s]+ *(?:(?<= )[^\n\]<>]*?|))\n([^[\]<>{}\n=@\/]*?) *\n*\]/i',
255 | '/\[(\w+:\/\/[^][<>"\s]+) +([Cc]lick here|[Hh]ere|\W|→|[ -\/;-@]) *\]/i',
256 | ),
257 | array(
258 | 'http://',
259 | "$1 ''",
260 | '[$1 $2]',
261 | '$2 [$1]',
262 | ),
263 | $text
264 | );
265 |
266 | $text = preg_replace( '/(\[\[(?:File|Image):[^][<>{|}]+)#(|filehistory|filelinks|file)(?=[\]|])/i', '$1', $text );
267 |
268 | $text = preg_replace( '/\[http://(www\.toolserver\.org|toolserver\.org|tools\.wikimedia\.org|tools\.wikimedia\.de)/([^][<>"\s;?]*)\?? ([^]\n]+)\]/', '[[tools:$2|$3]]', $text );
269 |
270 | return $text;
271 |
272 | }
273 |
274 | /**
275 | * @Fixme Method getWiki() not found.
276 | *
277 | * @see getWiki()
278 | *
279 | * @param string $text
280 | * @param string $title
281 | * @return mixed
282 | */
283 | public static function fixTypos( $text, $title ) {
284 |
285 | if( !count( self::$typo_list ) ) {
286 | global $script;
287 |
288 | $str = $script->getWiki()->initPage( 'Wikipedia:AutoWikiBrowser/Typos' )->get_text();
289 |
290 | foreach( explode( "\n", $str ) as $line ){
291 | if( substr( $line, 0, 5 ) == "/', $line, $m );
294 |
295 | if( !empty( $m[2] ) && !empty( $m[3] ) ) {
296 | self::$typo_list[] = array( 'word' => $m[1], 'find' => $m[2], 'replace' => $m[3] );
297 | }
298 | //
299 | }
300 | }
301 |
302 | }
303 |
304 | $run_times = array();
305 |
306 | shuffle( self::$typo_list ); //So that if it quits randomly, it will give equal prejudice to each typo.
307 |
308 | if( !count( self::$typo_list ) || preg_match( '/133t|-ology|\\(sic\\)|\\[sic\\]|\\[\'\'sic\'\'\\]|\\{\\{sic\\}\\}|spellfixno/', $text ) ) return $text;
309 |
310 | foreach( self::$typo_list as $typo ){
311 | //Skip typos in links
312 | $time = microtime( 1 );
313 |
314 | if( @preg_match( '/' . $typo['find'] . '/S', $title ) ) continue; //Skip if matches title
315 |
316 | if( @preg_match( "/(\{\{|\[\[)[^\[\]\r\n\|\{\}]*?" . $typo['find'] . "[^\[\]\r\n\|\{\}]*?(\]\]|\}\})/S", $text ) ) continue;
317 |
318 | $text2 = @preg_replace( '/' . $typo['find'] . '/S', $typo['replace'], $text );
319 | if( !is_null( $text2 ) ) $text = $text2;
320 | $run_times[$typo['word']] = number_format( microtime( 1 ) - $time, 2 );
321 | }
322 | return $text;
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/Includes/XMLParse.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class XMLParse {
21 |
22 | /**
23 | * Converts an XML url or string to a PHP array format
24 | *
25 | * @static
26 | * @access public
27 | * @param string $data Either an url to an xml file, or a raw XML string. Peachy will autodetect which is which.
28 | * @return array Parsed XML
29 | * @throws BadEntryError
30 | * @throws DependencyError
31 | * @throws HookError
32 | * @throws XMLError
33 | */
34 | public static function load( $data ) {
35 | $http = HTTP::getDefaultInstance();
36 |
37 | if( !function_exists( 'simplexml_load_string' ) ) {
38 | throw new DependencyError( "SimpleXML", "http://us.php.net/manual/en/book.simplexml.php" );
39 | }
40 |
41 | libxml_use_internal_errors( true );
42 |
43 | if( in_string( "get( $data );
47 | }
48 |
49 | Hooks::runHook( 'PreSimpleXMLLoad', array( &$xmlout ) );
50 |
51 | $xml = simplexml_load_string( $xmlout );
52 |
53 | Hooks::runHook( 'PostSimpleXMLLoad', array( &$xml ) );
54 |
55 | if( !$xml ) {
56 | foreach( libxml_get_errors() as $error ){
57 | throw new XMLError( $error );
58 | }
59 | }
60 |
61 | $outArr = array();
62 |
63 | $namespaces = $xml->getNamespaces( true );
64 | $namespaces['default'] = '';
65 |
66 | self::recurse( $xml, $outArr, $namespaces );
67 |
68 | libxml_clear_errors();
69 |
70 | return $outArr;
71 | }
72 |
73 | /**
74 | * @param SimpleXMLElement $xml
75 | * @param $arr
76 | * @param $namespaces
77 | */
78 | private static function recurse( $xml, &$arr, $namespaces ) {
79 |
80 | foreach( $namespaces as $namespace ){
81 |
82 | foreach( $xml->children( $namespace ) as $elementName => $node ){
83 |
84 | $key = count( $arr );
85 |
86 | if( isset( $arr['_name'] ) ) $key--;
87 | if( isset( $arr['_attributes'] ) ) $key--;
88 |
89 | $arr[$key] = array();
90 | $arr[$key]['_name'] = $elementName;
91 |
92 | $arr[$key]['_attributes'] = array();
93 |
94 | foreach( $node->attributes( $namespace ) as $aname => $avalue ){
95 | $arr[$key]['_attributes'][$aname] = trim( $avalue );
96 | }
97 |
98 | if( !count( $arr[$key]['_attributes'] ) ) {
99 | unset( $arr[$key]['_attributes'] );
100 | }
101 |
102 | $text = trim( (string)$node );
103 |
104 | if( strlen( $text ) > 0 ) $arr[$key]['_text'] = $text;
105 |
106 | self::recurse( $node, $arr[$key], $namespaces );
107 |
108 | if( count( $arr[$key] ) == 1 && isset( $arr[$key]['_text'] ) ) {
109 | $arr[$key] = $arr[$key]['_text'];
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Includes/lime.php:
--------------------------------------------------------------------------------
1 |
10 | *
11 | * For the full copyright and license information, please view the LICENSE
12 | * file that was distributed with this source code.
13 | */
14 |
15 | lime_colorizer::style('ERROR', array('bg' => 'red', 'fg' => 'white', 'bold' => true));
16 | lime_colorizer::style('INFO', array('fg' => 'green', 'bold' => true));
17 | lime_colorizer::style('TRACE', array('fg' => 'green', 'bold' => true));
18 | lime_colorizer::style('PARAMETER', array('fg' => 'cyan'));
19 | lime_colorizer::style('COMMENT', array('fg' => 'yellow'));
20 |
21 | lime_colorizer::style('GREEN_BAR', array('fg' => 'white', 'bg' => 'green', 'bold' => true));
22 | lime_colorizer::style('RED_BAR', array('fg' => 'white', 'bg' => 'red', 'bold' => true));
23 | lime_colorizer::style('YELLOW_BAR', array('fg' => 'black', 'bg' => 'yellow', 'bold' => true));
24 | lime_colorizer::style('INFO_BAR', array('fg' => 'cyan', 'bold' => true));
25 |
26 | lime_colorizer::style('DIFF_DELETED', array('fg' => 'red', 'bold' => true));
27 | lime_colorizer::style('DIFF_ADDED', array('fg' => 'green', 'bold' => true));
28 |
--------------------------------------------------------------------------------
/Includes/limeColorizer.php:
--------------------------------------------------------------------------------
1 | colors_supported = true;
20 | } else {
21 | // colors are supported on windows with ansicon or on tty consoles
22 | if (DIRECTORY_SEPARATOR == '\\') {
23 | $this->colors_supported = false !== getenv('ANSICON');
24 | } else {
25 | $this->colors_supported = function_exists('posix_isatty') && @posix_isatty(STDOUT);
26 | }
27 | }
28 | }
29 |
30 | /**
31 | * @param $name
32 | * @param array $options
33 | */
34 | public static function style($name, $options = array())
35 | {
36 | self::$styles[$name] = $options;
37 | }
38 |
39 | /**
40 | * @param string $text
41 | * @param array $parameters
42 | * @return string
43 | */
44 | public function colorize($text = '', $parameters = array())
45 | {
46 |
47 | if (!$this->colors_supported) {
48 | return $text;
49 | }
50 |
51 | static $options = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8);
52 | static $foreground = array(
53 | 'black' => 30,
54 | 'red' => 31,
55 | 'green' => 32,
56 | 'yellow' => 33,
57 | 'blue' => 34,
58 | 'magenta' => 35,
59 | 'cyan' => 36,
60 | 'white' => 37
61 | );
62 | static $background = array(
63 | 'black' => 40,
64 | 'red' => 41,
65 | 'green' => 42,
66 | 'yellow' => 43,
67 | 'blue' => 44,
68 | 'magenta' => 45,
69 | 'cyan' => 46,
70 | 'white' => 47
71 | );
72 |
73 | !is_array($parameters) && isset(self::$styles[$parameters]) and $parameters = self::$styles[$parameters];
74 |
75 | $codes = array();
76 | isset($parameters['fg']) and $codes[] = $foreground[$parameters['fg']];
77 | isset($parameters['bg']) and $codes[] = $background[$parameters['bg']];
78 | foreach ($options as $option => $value) {
79 | isset($parameters[$option]) && $parameters[$option] and $codes[] = $value;
80 | }
81 |
82 | return "\033[" . implode(';', $codes) . 'm' . $text . "\033[0m";
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Includes/limeCoverage.php:
--------------------------------------------------------------------------------
1 | harness = $harness;
25 |
26 | if (!function_exists('xdebug_start_code_coverage')) {
27 | throw new Exception('You must install and enable xdebug before using lime coverage.');
28 | }
29 |
30 | if (!ini_get('xdebug.extended_info')) {
31 | throw new Exception('You must set xdebug.extended_info to 1 in your php.ini to use lime coverage.');
32 | }
33 | }
34 |
35 | /**
36 | * @throws Exception
37 | */
38 | public function run()
39 | {
40 | if (!count($this->harness->files)) {
41 | throw new Exception('You must register some test files before running coverage!');
42 | }
43 |
44 | if (!count($this->files)) {
45 | throw new Exception('You must register some files to cover!');
46 | }
47 |
48 | $this->coverage = array();
49 |
50 | $this->process($this->harness->files);
51 |
52 | $this->output($this->files);
53 | }
54 |
55 | /**
56 | * @param $files
57 | * @throws Exception
58 | */
59 | public function process($files)
60 | {
61 | if (!is_array($files)) {
62 | $files = array($files);
63 | }
64 |
65 | $tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test.php';
66 | foreach ($files as $file) {
67 | $tmp = <<'.serialize(xdebug_get_code_coverage()).'';
72 | EOF;
73 | file_put_contents($tmp_file, $tmp);
74 | ob_start();
75 | // see http://trac.symfony-project.org/ticket/5437 for the explanation on the weird "cd" thing
76 | passthru(sprintf('cd & %s %s 2>&1', escapeshellarg($this->harness->php_cli), escapeshellarg($tmp_file)),
77 | $return);
78 | $retval = ob_get_clean();
79 |
80 | if (0 != $return) // test exited without success
81 | {
82 | // something may have gone wrong, we should warn the user so they know
83 | // it's a bug in their code and not symfony's
84 |
85 | $this->harness->output->echoln(sprintf('Warning: %s returned status %d, results may be inaccurate',
86 | $file, $return), 'ERROR');
87 | }
88 |
89 | if (false === $cov = @unserialize(substr($retval, strpos($retval, '') + 9,
90 | strpos($retval, '') - 9))
91 | ) {
92 | if (0 == $return) {
93 | // failed to serialize, but PHP said it should of worked.
94 | // something is seriously wrong, so abort with exception
95 | throw new Exception(sprintf('Unable to unserialize coverage for file "%s"', $file));
96 | } else {
97 | // failed to serialize, but PHP warned us that this might have happened.
98 | // so we should ignore and move on
99 | continue; // continue foreach loop through $this->harness->files
100 | }
101 | }
102 |
103 | foreach ($cov as $file => $lines) {
104 | if (!isset($this->coverage[$file])) {
105 | $this->coverage[$file] = $lines;
106 | continue;
107 | }
108 |
109 | foreach ($lines as $line => $flag) {
110 | if ($flag == 1) {
111 | $this->coverage[$file][$line] = 1;
112 | }
113 | }
114 | }
115 | }
116 |
117 | if (file_exists($tmp_file)) {
118 | unlink($tmp_file);
119 | }
120 | }
121 |
122 | /**
123 | * @param $files
124 | */
125 | public function output($files)
126 | {
127 | ksort($this->coverage);
128 | $total_php_lines = 0;
129 | $total_covered_lines = 0;
130 | foreach ($files as $file) {
131 | $file = realpath($file);
132 | $is_covered = isset($this->coverage[$file]);
133 | $cov = isset($this->coverage[$file]) ? $this->coverage[$file] : array();
134 | $covered_lines = array();
135 | $missing_lines = array();
136 | $output = null;
137 |
138 | foreach ($cov as $line => $flag) {
139 | switch ($flag) {
140 | case 1:
141 | $covered_lines[] = $line;
142 | break;
143 | case -1:
144 | $missing_lines[] = $line;
145 | break;
146 | }
147 | }
148 |
149 | $total_lines = count($covered_lines) + count($missing_lines);
150 | if (!$total_lines) {
151 | // probably means that the file is not covered at all!
152 | $total_lines = count($this->get_php_lines(file_get_contents($file)));
153 | }
154 |
155 | $output = $this->harness->output;
156 | $percent = $total_lines ? count($covered_lines) * 100 / $total_lines : 0;
157 |
158 | $total_php_lines += $total_lines;
159 | $total_covered_lines += count($covered_lines);
160 |
161 | $relative_file = $this->get_relative_file($file);
162 | $output->echoln(sprintf("%-70s %3.0f%%", substr($relative_file, -min(70, strlen($relative_file))),
163 | $percent), $percent == 100 ? 'INFO' : ($percent > 90 ? 'PARAMETER' : ($percent < 20 ? 'ERROR' : '')));
164 | if ($this->verbose && $is_covered && $percent != 100) {
165 | $output->comment(sprintf("missing: %s", $this->format_range($missing_lines)));
166 | }
167 | }
168 |
169 | $output->echoln(sprintf("TOTAL COVERAGE: %3.0f%%",
170 | $total_php_lines ? $total_covered_lines * 100 / $total_php_lines : 0));
171 | }
172 |
173 | /**
174 | * @param $content
175 | * @return array
176 | */
177 | public static function get_php_lines($content)
178 | {
179 | if (is_readable($content)) {
180 | $content = file_get_contents($content);
181 | }
182 |
183 | $tokens = token_get_all($content);
184 | $php_lines = array();
185 | $current_line = 1;
186 | $in_class = false;
187 | $in_function = false;
188 | $in_function_declaration = false;
189 | $end_of_current_expr = true;
190 | $open_braces = 0;
191 | foreach ($tokens as $token) {
192 | if (is_string($token)) {
193 | switch ($token) {
194 | case '=':
195 | if (false === $in_class || (false !== $in_function && !$in_function_declaration)) {
196 | $php_lines[$current_line] = true;
197 | }
198 | break;
199 | case '{':
200 | ++$open_braces;
201 | $in_function_declaration = false;
202 | break;
203 | case ';':
204 | $in_function_declaration = false;
205 | $end_of_current_expr = true;
206 | break;
207 | case '}':
208 | $end_of_current_expr = true;
209 | --$open_braces;
210 | if ($open_braces == $in_class) {
211 | $in_class = false;
212 | }
213 | if ($open_braces == $in_function) {
214 | $in_function = false;
215 | }
216 | break;
217 | }
218 |
219 | continue;
220 | }
221 |
222 | list($id, $text) = $token;
223 |
224 | switch ($id) {
225 | case T_CURLY_OPEN:
226 | case T_DOLLAR_OPEN_CURLY_BRACES:
227 | ++$open_braces;
228 | break;
229 | case T_WHITESPACE:
230 | case T_OPEN_TAG:
231 | case T_CLOSE_TAG:
232 | $end_of_current_expr = true;
233 | $current_line += count(explode("\n", $text)) - 1;
234 | break;
235 | case T_COMMENT:
236 | case T_DOC_COMMENT:
237 | $current_line += count(explode("\n", $text)) - 1;
238 | break;
239 | case T_CLASS:
240 | $in_class = $open_braces;
241 | break;
242 | case T_FUNCTION:
243 | $in_function = $open_braces;
244 | $in_function_declaration = true;
245 | break;
246 | case T_AND_EQUAL:
247 | case T_BREAK:
248 | case T_CASE:
249 | case T_CATCH:
250 | case T_CLONE:
251 | case T_CONCAT_EQUAL:
252 | case T_CONTINUE:
253 | case T_DEC:
254 | case T_DECLARE:
255 | case T_DEFAULT:
256 | case T_DIV_EQUAL:
257 | case T_DO:
258 | case T_ECHO:
259 | case T_ELSEIF:
260 | case T_EMPTY:
261 | case T_ENDDECLARE:
262 | case T_ENDFOR:
263 | case T_ENDFOREACH:
264 | case T_ENDIF:
265 | case T_ENDSWITCH:
266 | case T_ENDWHILE:
267 | case T_EVAL:
268 | case T_EXIT:
269 | case T_FOR:
270 | case T_FOREACH:
271 | case T_GLOBAL:
272 | case T_IF:
273 | case T_INC:
274 | case T_INCLUDE:
275 | case T_INCLUDE_ONCE:
276 | case T_INSTANCEOF:
277 | case T_ISSET:
278 | case T_IS_EQUAL:
279 | case T_IS_GREATER_OR_EQUAL:
280 | case T_IS_IDENTICAL:
281 | case T_IS_NOT_EQUAL:
282 | case T_IS_NOT_IDENTICAL:
283 | case T_IS_SMALLER_OR_EQUAL:
284 | case T_LIST:
285 | case T_LOGICAL_AND:
286 | case T_LOGICAL_OR:
287 | case T_LOGICAL_XOR:
288 | case T_MINUS_EQUAL:
289 | case T_MOD_EQUAL:
290 | case T_MUL_EQUAL:
291 | case T_NEW:
292 | case T_OBJECT_OPERATOR:
293 | case T_OR_EQUAL:
294 | case T_PLUS_EQUAL:
295 | case T_PRINT:
296 | case T_REQUIRE:
297 | case T_REQUIRE_ONCE:
298 | case T_RETURN:
299 | case T_SL:
300 | case T_SL_EQUAL:
301 | case T_SR:
302 | case T_SR_EQUAL:
303 | case T_SWITCH:
304 | case T_THROW:
305 | case T_TRY:
306 | case T_UNSET:
307 | case T_UNSET_CAST:
308 | case T_USE:
309 | case T_WHILE:
310 | case T_XOR_EQUAL:
311 | $php_lines[$current_line] = true;
312 | $end_of_current_expr = false;
313 | break;
314 | default:
315 | if (false === $end_of_current_expr) {
316 | $php_lines[$current_line] = true;
317 | }
318 | }
319 | }
320 |
321 | return $php_lines;
322 | }
323 |
324 | /**
325 | * @param $content
326 | * @param $cov
327 | * @return array
328 | */
329 | public function compute($content, $cov)
330 | {
331 | $php_lines = self::get_php_lines($content);
332 |
333 | // we remove from $cov non php lines
334 | foreach (array_diff_key($cov, $php_lines) as $line => $tmp) {
335 | unset($cov[$line]);
336 | }
337 |
338 | return array($cov, $php_lines);
339 | }
340 |
341 | /**
342 | * @param $lines
343 | * @return string
344 | */
345 | public function format_range($lines)
346 | {
347 | sort($lines);
348 | $formatted = '';
349 | $first = -1;
350 | $last = -1;
351 | foreach ($lines as $line) {
352 | if ($last + 1 != $line) {
353 | if ($first != -1) {
354 | $formatted .= $first == $last ? "$first " : "[$first - $last] ";
355 | }
356 | $first = $line;
357 | $last = $line;
358 | } else {
359 | $last = $line;
360 | }
361 | }
362 | if ($first != -1) {
363 | $formatted .= $first == $last ? "$first " : "[$first - $last] ";
364 | }
365 |
366 | return $formatted;
367 | }
368 | }
369 |
--------------------------------------------------------------------------------
/Includes/limeHarness.php:
--------------------------------------------------------------------------------
1 | $options);
25 | }
26 |
27 | $this->options = array_merge(array(
28 | 'php_cli' => null,
29 | 'force_colors' => false,
30 | 'output' => null,
31 | 'verbose' => false,
32 | ), $options);
33 |
34 | $this->php_cli = $this->find_php_cli($this->options['php_cli']);
35 | $this->output = $this->options['output'] ? $this->options['output'] : new lime_output($this->options['force_colors']);
36 | }
37 |
38 | /**
39 | * @param null $php_cli
40 | * @return null|string
41 | * @throws Exception
42 | */
43 | protected function find_php_cli($php_cli = null)
44 | {
45 | if (is_null($php_cli)) {
46 | if (getenv('PHP_PATH')) {
47 | $php_cli = getenv('PHP_PATH');
48 |
49 | if (!is_executable($php_cli)) {
50 | throw new Exception('The defined PHP_PATH environment variable is not a valid PHP executable.');
51 | }
52 | } else {
53 | $php_cli = PHP_BINDIR . DIRECTORY_SEPARATOR . 'php';
54 | }
55 | }
56 |
57 | if (is_executable($php_cli)) {
58 | return $php_cli;
59 | }
60 |
61 | $path = getenv('PATH') ? getenv('PATH') : getenv('Path');
62 | $exe_suffixes = DIRECTORY_SEPARATOR == '\\' ? (getenv('PATHEXT') ? explode(PATH_SEPARATOR,
63 | getenv('PATHEXT')) : array('.exe', '.bat', '.cmd', '.com')) : array('');
64 | foreach (array('php5', 'php') as $php_cli) {
65 | foreach ($exe_suffixes as $suffix) {
66 | foreach (explode(PATH_SEPARATOR, $path) as $dir) {
67 | $file = $dir . DIRECTORY_SEPARATOR . $php_cli . $suffix;
68 | if (is_executable($file)) {
69 | return $file;
70 | }
71 | }
72 | }
73 | }
74 |
75 | throw new Exception("Unable to find PHP executable.");
76 | }
77 |
78 | /**
79 | * @return array
80 | */
81 | public function to_array()
82 | {
83 | $results = array();
84 | foreach ($this->stats['files'] as $stat) {
85 | $results = array_merge($results, $stat['output']);
86 | }
87 |
88 | return $results;
89 | }
90 |
91 | /**
92 | * @return string
93 | */
94 | public function to_xml()
95 | {
96 | return lime_test::to_xml($this->to_array());
97 | }
98 |
99 | /**
100 | * @return bool
101 | * @throws Exception
102 | */
103 | public function run()
104 | {
105 | if (!count($this->files)) {
106 | throw new Exception('You must register some test files before running them!');
107 | }
108 |
109 | // sort the files to be able to predict the order
110 | sort($this->files);
111 |
112 | $this->stats = array(
113 | 'files' => array(),
114 | 'failed_files' => array(),
115 | 'failed_tests' => 0,
116 | 'total' => 0,
117 | );
118 |
119 | foreach ($this->files as $file) {
120 | $this->stats['files'][$file] = array();
121 | $stats = &$this->stats['files'][$file];
122 |
123 | $relative_file = $this->get_relative_file($file);
124 |
125 | $test_file = tempnam(sys_get_temp_dir(), 'lime');
126 | $result_file = tempnam(sys_get_temp_dir(), 'lime');
127 | file_put_contents($test_file, <<&1', escapeshellarg($this->php_cli), escapeshellarg($test_file)), $return);
141 | ob_end_clean();
142 | unlink($test_file);
143 |
144 | $output = file_get_contents($result_file);
145 | $stats['output'] = $output ? unserialize($output) : '';
146 | if (!$stats['output']) {
147 | $stats['output'] = array(
148 | array(
149 | 'file' => $file,
150 | 'tests' => array(),
151 | 'stats' => array(
152 | 'plan' => 1,
153 | 'total' => 1,
154 | 'failed' => array(0),
155 | 'passed' => array(),
156 | 'skipped' => array(),
157 | 'errors' => array()
158 | )
159 | )
160 | );
161 | }
162 | unlink($result_file);
163 |
164 | $file_stats = &$stats['output'][0]['stats'];
165 |
166 | $delta = 0;
167 | if ($return > 0) {
168 | $stats['status'] = $file_stats['errors'] ? 'errors' : 'dubious';
169 | $stats['status_code'] = $return;
170 | } else {
171 | $this->stats['total'] += $file_stats['total'];
172 |
173 | if (!$file_stats['plan']) {
174 | $file_stats['plan'] = $file_stats['total'];
175 | }
176 |
177 | $delta = $file_stats['plan'] - $file_stats['total'];
178 | if (0 != $delta) {
179 | $stats['status'] = $file_stats['errors'] ? 'errors' : 'dubious';
180 | $stats['status_code'] = 255;
181 | } else {
182 | $stats['status'] = $file_stats['failed'] ? 'not ok' : ($file_stats['errors'] ? 'errors' : 'ok');
183 | $stats['status_code'] = 0;
184 | }
185 | }
186 |
187 | $this->output->echoln(sprintf('%s%s%s', substr($relative_file, -min(67, strlen($relative_file))),
188 | str_repeat('.', 70 - min(67, strlen($relative_file))), $stats['status']));
189 |
190 | if ('dubious' == $stats['status']) {
191 | $this->output->echoln(sprintf(' Test returned status %s', $stats['status_code']));
192 | }
193 |
194 | if ('ok' != $stats['status']) {
195 | $this->stats['failed_files'][] = $file;
196 | }
197 |
198 | if ($delta > 0) {
199 | $this->output->echoln(sprintf(' Looks like you planned %d tests but only ran %d.',
200 | $file_stats['plan'], $file_stats['total']));
201 |
202 | $this->stats['failed_tests'] += $delta;
203 | $this->stats['total'] += $delta;
204 | } else {
205 | if ($delta < 0) {
206 | $this->output->echoln(sprintf(' Looks like you planned %s test but ran %s extra.',
207 | $file_stats['plan'], $file_stats['total'] - $file_stats['plan']));
208 | }
209 | }
210 |
211 | if (false !== $file_stats && $file_stats['failed']) {
212 | $this->stats['failed_tests'] += count($file_stats['failed']);
213 |
214 | $this->output->echoln(sprintf(" Failed tests: %s", implode(', ', $file_stats['failed'])));
215 | }
216 |
217 | if (false !== $file_stats && $file_stats['errors']) {
218 | $this->output->echoln(' Errors:');
219 |
220 | $error_count = count($file_stats['errors']);
221 | for ($i = 0; $i < 3 && $i < $error_count; ++$i) {
222 | $this->output->echoln(' - ' . $file_stats['errors'][$i]['message'], null, false);
223 | }
224 | if ($error_count > 3) {
225 | $this->output->echoln(sprintf(' ... and %s more', $error_count - 3));
226 | }
227 | }
228 | }
229 |
230 | if (count($this->stats['failed_files'])) {
231 | $format = "%-30s %4s %5s %5s %5s %s";
232 | $this->output->echoln(sprintf($format, 'Failed Test', 'Stat', 'Total', 'Fail', 'Errors', 'List of Failed'));
233 | $this->output->echoln("--------------------------------------------------------------------------");
234 | foreach ($this->stats['files'] as $file => $stat) {
235 | if (!in_array($file, $this->stats['failed_files'])) {
236 | continue;
237 | }
238 | $relative_file = $this->get_relative_file($file);
239 |
240 | if (isset($stat['output'][0])) {
241 | $this->output->echoln(sprintf($format, substr($relative_file, -min(30, strlen($relative_file))),
242 | $stat['status_code'],
243 | count($stat['output'][0]['stats']['failed']) + count($stat['output'][0]['stats']['passed']),
244 | count($stat['output'][0]['stats']['failed']), count($stat['output'][0]['stats']['errors']),
245 | implode(' ', $stat['output'][0]['stats']['failed'])));
246 | } else {
247 | $this->output->echoln(sprintf($format, substr($relative_file, -min(30, strlen($relative_file))),
248 | $stat['status_code'], '', '', ''));
249 | }
250 | }
251 |
252 | $this->output->red_bar(sprintf('Failed %d/%d test scripts, %.2f%% okay. %d/%d subtests failed, %.2f%% okay.',
253 | $nb_failed_files = count($this->stats['failed_files']),
254 | $nb_files = count($this->files),
255 | ($nb_files - $nb_failed_files) * 100 / $nb_files,
256 | $nb_failed_tests = $this->stats['failed_tests'],
257 | $nb_tests = $this->stats['total'],
258 | $nb_tests > 0 ? ($nb_tests - $nb_failed_tests) * 100 / $nb_tests : 0
259 | ));
260 |
261 | if ($this->options['verbose']) {
262 | foreach ($this->to_array() as $testsuite) {
263 | $first = true;
264 | foreach ($testsuite['stats']['failed'] as $testcase) {
265 | if (!isset($testsuite['tests'][$testcase]['file'])) {
266 | continue;
267 | }
268 |
269 | if ($first) {
270 | $this->output->echoln('');
271 | $this->output->error($this->get_relative_file($testsuite['file']) . $this->extension);
272 | $first = false;
273 | }
274 |
275 | $this->output->comment(sprintf(' at %s line %s',
276 | $this->get_relative_file($testsuite['tests'][$testcase]['file']) . $this->extension,
277 | $testsuite['tests'][$testcase]['line']));
278 | $this->output->info(' ' . $testsuite['tests'][$testcase]['message']);
279 | $this->output->echoln($testsuite['tests'][$testcase]['error'], null, false);
280 | }
281 | }
282 | }
283 | } else {
284 | $this->output->green_bar(' All tests successful.');
285 | $this->output->green_bar(sprintf(' Files=%d, Tests=%d', count($this->files), $this->stats['total']));
286 | }
287 |
288 | return $this->stats['failed_files'] ? false : true;
289 | }
290 |
291 | /**
292 | * @return array
293 | */
294 | public function get_failed_files()
295 | {
296 | return isset($this->stats['failed_files']) ? $this->stats['failed_files'] : array();
297 | }
298 | }
--------------------------------------------------------------------------------
/Includes/limeOutput.php:
--------------------------------------------------------------------------------
1 | colorizer = new lime_colorizer($force_colors);
19 | $this->base_dir = $base_dir === null ? getcwd() : $base_dir;
20 | }
21 |
22 | /**
23 | * Produces an Echo
24 | */
25 | public function diag()
26 | {
27 | $messages = func_get_args();
28 | foreach ($messages as $message) {
29 | echo $this->colorizer->colorize('# ' . join("\n# ", (array)$message), 'COMMENT') . "\n";
30 | }
31 | }
32 |
33 | /**
34 | * @param $message
35 | */
36 | public function comment($message)
37 | {
38 | echo $this->colorizer->colorize(sprintf('# %s', $message), 'COMMENT') . "\n";
39 | }
40 |
41 | /**
42 | * @param $message
43 | */
44 | public function info($message)
45 | {
46 | echo $this->colorizer->colorize(sprintf('> %s', $message), 'INFO_BAR') . "\n";
47 | }
48 |
49 | /**
50 | * @param $message
51 | * @param null $file
52 | * @param null $line
53 | * @param array $traces
54 | */
55 | public function error($message, $file = null, $line = null, $traces = array())
56 | {
57 | if ($file !== null) {
58 | $message .= sprintf("\n(in %s on line %s)", $file, $line);
59 | }
60 |
61 | // some error messages contain absolute file paths
62 | $message = $this->strip_base_dir($message);
63 |
64 | $space = $this->colorizer->colorize(str_repeat(' ', 71), 'RED_BAR') . "\n";
65 | $message = trim($message);
66 | $message = wordwrap($message, 66, "\n");
67 |
68 | echo "\n" . $space;
69 | foreach (explode("\n", $message) as $message_line) {
70 | echo $this->colorizer->colorize(str_pad(' ' . $message_line, 71, ' '), 'RED_BAR') . "\n";
71 | }
72 | echo $space . "\n";
73 |
74 | if (count($traces) > 0) {
75 | echo $this->colorizer->colorize('Exception trace:', 'COMMENT') . "\n";
76 |
77 | $this->print_trace(null, $file, $line);
78 |
79 | foreach ($traces as $trace) {
80 | if (array_key_exists('class', $trace)) {
81 | $method = sprintf('%s%s%s()', $trace['class'], $trace['type'], $trace['function']);
82 | } else {
83 | $method = sprintf('%s()', $trace['function']);
84 | }
85 |
86 | if (array_key_exists('file', $trace)) {
87 | $this->print_trace($method, $trace['file'], $trace['line']);
88 | } else {
89 | $this->print_trace($method);
90 | }
91 | }
92 |
93 | echo "\n";
94 | }
95 | }
96 |
97 | /**
98 | * @param string $method
99 | */
100 | protected function print_trace($method = null, $file = null, $line = null)
101 | {
102 | if (!is_null($method)) {
103 | $method .= ' ';
104 | }
105 |
106 | echo ' ' . $method . 'at ';
107 |
108 | if (!is_null($file) && !is_null($line)) {
109 | printf("%s:%s\n", $this->colorizer->colorize($this->strip_base_dir($file), 'TRACE'),
110 | $this->colorizer->colorize($line, 'TRACE'));
111 | } else {
112 | echo "[internal function]\n";
113 | }
114 | }
115 |
116 | /**
117 | * @param $message
118 | * @param null $colorizer_parameter
119 | * @param bool|true $colorize
120 | */
121 | public function echoln($message, $colorizer_parameter = null, $colorize = true)
122 | {
123 | if ($colorize) {
124 | $message = preg_replace('/(?:^|\.)((?:not ok|dubious|errors) *\d*)\b/e',
125 | '$this->colorizer->colorize(\'$1\', \'ERROR\')', $message);
126 | $message = preg_replace('/(?:^|\.)(ok *\d*)\b/e', '$this->colorizer->colorize(\'$1\', \'INFO\')', $message);
127 | $message = preg_replace('/"(.+?)"/e', '$this->colorizer->colorize(\'$1\', \'PARAMETER\')', $message);
128 | $message = preg_replace('/(\->|\:\:)?([a-zA-Z0-9_]+?)\(\)/e',
129 | '$this->colorizer->colorize(\'$1$2()\', \'PARAMETER\')', $message);
130 | }
131 |
132 | echo ($colorizer_parameter ? $this->colorizer->colorize($message, $colorizer_parameter) : $message) . "\n";
133 | }
134 |
135 | /**
136 | * @param string $message
137 | */
138 | public function green_bar($message)
139 | {
140 | echo $this->colorizer->colorize($message . str_repeat(' ', 71 - min(71, strlen($message))), 'GREEN_BAR') . "\n";
141 | }
142 |
143 | /**
144 | * @param string $message
145 | */
146 | public function red_bar($message)
147 | {
148 | echo $this->colorizer->colorize($message . str_repeat(' ', 71 - min(71, strlen($message))), 'RED_BAR') . "\n";
149 | }
150 |
151 | /**
152 | * @return string
153 | */
154 | protected function strip_base_dir($text)
155 | {
156 | return str_replace(DIRECTORY_SEPARATOR, '/',
157 | str_replace(realpath($this->base_dir) . DIRECTORY_SEPARATOR, '', $text));
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Includes/limeRegistration.php:
--------------------------------------------------------------------------------
1 | files[] = realpath($f_or_d);
22 | } elseif (is_dir($f_or_d)) {
23 | $this->register_dir($f_or_d);
24 | } else {
25 | throw new Exception(sprintf('The file or directory "%s" does not exist.', $f_or_d));
26 | }
27 | }
28 | }
29 |
30 | /**
31 | * @param $glob
32 | */
33 | public function register_glob($glob)
34 | {
35 | if ($dirs = glob($glob)) {
36 | foreach ($dirs as $file) {
37 | $this->files[] = realpath($file);
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * @param $directory
44 | * @throws Exception
45 | */
46 | public function register_dir($directory)
47 | {
48 | if (!is_dir($directory)) {
49 | throw new Exception(sprintf('The directory "%s" does not exist.', $directory));
50 | }
51 |
52 | $files = array();
53 |
54 | $current_dir = opendir($directory);
55 | while ($entry = readdir($current_dir)) {
56 | if ($entry == '.' || $entry == '..') {
57 | continue;
58 | }
59 |
60 | if (is_dir($entry)) {
61 | $this->register_dir($entry);
62 | } elseif (preg_match('#' . $this->extension . '$#', $entry)) {
63 | $files[] = realpath($directory . DIRECTORY_SEPARATOR . $entry);
64 | }
65 | }
66 |
67 | $this->files = array_merge($this->files, $files);
68 | }
69 |
70 | /**
71 | * @param $file
72 | * @return mixed
73 | */
74 | protected function get_relative_file($file)
75 | {
76 | return str_replace(DIRECTORY_SEPARATOR, '/',
77 | str_replace(array(realpath($this->base_dir) . DIRECTORY_SEPARATOR, $this->extension), '', $file));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Includes/lime_test.php:
--------------------------------------------------------------------------------
1 |
8 | * @version SVN: $Id$
9 | */
10 | class lime_test
11 | {
12 | const EPSILON = 0.0000000001;
13 |
14 | protected $test_nb = 0;
15 | protected $output = null;
16 | protected $results = array();
17 | protected $options = array();
18 |
19 | static protected $all_results = array();
20 |
21 | /**
22 | * Constructor for the lime.php class
23 | *
24 | * @param null $plan
25 | * @param array $options
26 | */
27 | public function __construct($plan = null, $options = array())
28 | {
29 | // for BC
30 | if (!is_array($options)) {
31 | $options = array('output' => $options);
32 | }
33 |
34 | $this->options = array_merge(array(
35 | 'force_colors' => false,
36 | 'output' => null,
37 | 'verbose' => false,
38 | 'error_reporting' => false,
39 | ), $options);
40 |
41 | $this->output = $this->options['output'] ? $this->options['output'] : new lime_output($this->options['force_colors']);
42 |
43 | $caller = $this->find_caller(debug_backtrace());
44 | self::$all_results[] = array(
45 | 'file' => $caller[0],
46 | 'tests' => array(),
47 | 'stats' => array(
48 | 'plan' => $plan,
49 | 'total' => 0,
50 | 'failed' => array(),
51 | 'passed' => array(),
52 | 'skipped' => array(),
53 | 'errors' => array()
54 | ),
55 | );
56 |
57 | $this->results = &self::$all_results[count(self::$all_results) - 1];
58 |
59 | null !== $plan and $this->output->echoln(sprintf("1..%d", $plan));
60 |
61 | set_error_handler(array($this, 'handle_error'));
62 | set_exception_handler(array($this, 'handle_exception'));
63 | }
64 |
65 | static public function reset()
66 | {
67 | self::$all_results = array();
68 | }
69 |
70 | /**
71 | * @return array
72 | */
73 | static public function to_array()
74 | {
75 | return self::$all_results;
76 | }
77 |
78 | /**
79 | * @param null $results
80 | * @return string
81 | */
82 | static public function to_xml($results = null)
83 | {
84 | if (is_null($results)) {
85 | $results = self::$all_results;
86 | }
87 |
88 | $dom = new DOMDocument('1.0', 'UTF-8');
89 | $dom->formatOutput = true;
90 | $dom->appendChild($testsuites = $dom->createElement('testsuites'));
91 |
92 | $failures = 0;
93 | $errors = 0;
94 | $skipped = 0;
95 | $assertions = 0;
96 |
97 | foreach ($results as $result) {
98 | $testsuites->appendChild($testsuite = $dom->createElement('testsuite'));
99 | $testsuite->setAttribute('name', basename($result['file'], '.php'));
100 | $testsuite->setAttribute('file', $result['file']);
101 | $testsuite->setAttribute('failures', count($result['stats']['failed']));
102 | $testsuite->setAttribute('errors', count($result['stats']['errors']));
103 | $testsuite->setAttribute('skipped', count($result['stats']['skipped']));
104 | $testsuite->setAttribute('tests', $result['stats']['plan']);
105 | $testsuite->setAttribute('assertions', $result['stats']['plan']);
106 |
107 | $failures += count($result['stats']['failed']);
108 | $errors += count($result['stats']['errors']);
109 | $skipped += count($result['stats']['skipped']);
110 | $assertions += $result['stats']['plan'];
111 |
112 | foreach ($result['tests'] as $test) {
113 | $testsuite->appendChild($testcase = $dom->createElement('testcase'));
114 | $testcase->setAttribute('name', $test['message']);
115 | $testcase->setAttribute('file', $test['file']);
116 | $testcase->setAttribute('line', $test['line']);
117 | $testcase->setAttribute('assertions', 1);
118 | if (!$test['status']) {
119 | $testcase->appendChild($failure = $dom->createElement('failure'));
120 | $failure->setAttribute('type', 'lime');
121 | if (isset($test['error'])) {
122 | $failure->appendChild($dom->createTextNode($test['error']));
123 | }
124 | }
125 | }
126 | }
127 |
128 | $testsuites->setAttribute('failures', $failures);
129 | $testsuites->setAttribute('errors', $errors);
130 | $testsuites->setAttribute('tests', $assertions);
131 | $testsuites->setAttribute('assertions', $assertions);
132 | $testsuites->setAttribute('skipped', $skipped);
133 |
134 | return $dom->saveXml();
135 | }
136 |
137 | /**
138 | *
139 | */
140 | public function __destruct()
141 | {
142 | $plan = $this->results['stats']['plan'];
143 | $passed = count($this->results['stats']['passed']);
144 | $failed = count($this->results['stats']['failed']);
145 | $total = $this->results['stats']['total'];
146 | is_null($plan) and $plan = $total and $this->output->echoln(sprintf("1..%d", $plan));
147 |
148 | if ($total > $plan) {
149 | $this->output->red_bar(sprintf("# Looks like you planned %d tests but ran %d extra.", $plan,
150 | $total - $plan));
151 | } elseif ($total < $plan) {
152 | $this->output->red_bar(sprintf("# Looks like you planned %d tests but only ran %d.", $plan, $total));
153 | }
154 |
155 | if ($failed) {
156 | $this->output->red_bar(sprintf("# Looks like you failed %d tests of %d.", $failed, $passed + $failed));
157 | } else {
158 | if ($total == $plan) {
159 | $this->output->green_bar("# Looks like everything went fine.");
160 | }
161 | }
162 |
163 | flush();
164 | }
165 |
166 | /**
167 | * Tests a condition and passes if it is true
168 | *
169 | * @param mixed $exp condition to test
170 | * @param string $message display output message when the test passes
171 | *
172 | * @return boolean
173 | */
174 | public function ok($exp, $message = '')
175 | {
176 | $this->update_stats();
177 |
178 | if ($result = (boolean)$exp) {
179 | $this->results['stats']['passed'][] = $this->test_nb;
180 | } else {
181 | $this->results['stats']['failed'][] = $this->test_nb;
182 | }
183 | $this->results['tests'][$this->test_nb]['message'] = $message;
184 | $this->results['tests'][$this->test_nb]['status'] = $result;
185 | $this->output->echoln(sprintf("%s %d%s", $result ? 'ok' : 'not ok', $this->test_nb,
186 | $message = $message ? sprintf('%s %s', 0 === strpos($message, '#') ? '' : ' -', $message) : ''));
187 |
188 | if (!$result) {
189 | $this->output->diag(sprintf(' Failed test (%s at line %d)',
190 | str_replace(getcwd(), '.', $this->results['tests'][$this->test_nb]['file']),
191 | $this->results['tests'][$this->test_nb]['line']));
192 | }
193 |
194 | return $result;
195 | }
196 |
197 | /**
198 | * Compares two values and passes if they are equal (==)
199 | *
200 | * @param mixed $exp1 left value
201 | * @param mixed $exp2 right value
202 | * @param string $message display output message when the test passes
203 | *
204 | * @return boolean
205 | */
206 | public function is($exp1, $exp2, $message = '')
207 | {
208 | if (is_object($exp1) || is_object($exp2)) {
209 | $value = $exp1 === $exp2;
210 | } else {
211 | if (is_float($exp1) && is_float($exp2)) {
212 | $value = abs($exp1 - $exp2) < self::EPSILON;
213 | } else {
214 | $value = $exp1 == $exp2;
215 | }
216 | }
217 |
218 | if (!$result = $this->ok($value, $message)) {
219 | $this->set_last_test_errors(array(
220 | sprintf(" got: %s", var_export($exp1, true)),
221 | sprintf(" expected: %s", var_export($exp2, true))
222 | ));
223 | }
224 |
225 | return $result;
226 | }
227 |
228 | /**
229 | * Compares two values and passes if they are not equal
230 | *
231 | * @param mixed $exp1 left value
232 | * @param mixed $exp2 right value
233 | * @param string $message display output message when the test passes
234 | *
235 | * @return boolean
236 | */
237 | public function isnt($exp1, $exp2, $message = '')
238 | {
239 | if (!$result = $this->ok($exp1 != $exp2, $message)) {
240 | $this->set_last_test_errors(array(
241 | sprintf(" %s", var_export($exp2, true)),
242 | ' ne',
243 | sprintf(" %s", var_export($exp2, true))
244 | ));
245 | }
246 |
247 | return $result;
248 | }
249 |
250 | /**
251 | * Tests a string against a regular expression
252 | *
253 | * @param string $exp value to test
254 | * @param string $regex the pattern to search for, as a string
255 | * @param string $message display output message when the test passes
256 | *
257 | * @return boolean
258 | */
259 | public function like($exp, $regex, $message = '')
260 | {
261 | if (!$result = $this->ok(preg_match($regex, $exp), $message)) {
262 | $this->set_last_test_errors(array(
263 | sprintf(" '%s'", $exp),
264 | sprintf(" doesn't match '%s'", $regex)
265 | ));
266 | }
267 |
268 | return $result;
269 | }
270 |
271 | /**
272 | * Checks that a string doesn't match a regular expression
273 | *
274 | * @param string $exp value to test
275 | * @param string $regex the pattern to search for, as a string
276 | * @param string $message display output message when the test passes
277 | *
278 | * @return boolean
279 | */
280 | public function unlike($exp, $regex, $message = '')
281 | {
282 | if (!$result = $this->ok(!preg_match($regex, $exp), $message)) {
283 | $this->set_last_test_errors(array(
284 | sprintf(" '%s'", $exp),
285 | sprintf(" matches '%s'", $regex)
286 | ));
287 | }
288 |
289 | return $result;
290 | }
291 |
292 | /**
293 | * Compares two arguments with an operator
294 | *
295 | * @param mixed $exp1 left value
296 | * @param string $op operator
297 | * @param mixed $exp2 right value
298 | * @param string $message display output message when the test passes
299 | *
300 | * @return boolean
301 | */
302 | public function cmp_ok($exp1, $op, $exp2, $message = '')
303 | {
304 | $result = false;
305 | $php = sprintf("\$result = \$exp1 $op \$exp2;");
306 | // under some unknown conditions the sprintf() call causes a segmentation fault
307 | // when placed directly in the eval() call
308 | eval($php);
309 |
310 | if (!$this->ok($result, $message)) {
311 | $this->set_last_test_errors(array(
312 | sprintf(" %s", str_replace("\n", '', var_export($exp1, true))),
313 | sprintf(" %s", $op),
314 | sprintf(" %s", str_replace("\n", '', var_export($exp2, true)))
315 | ));
316 | }
317 |
318 | return $result;
319 | }
320 |
321 | /**
322 | * Checks the availability of a method for an object or a class
323 | *
324 | * @param mixed $object an object instance or a class name
325 | * @param string|array $methods one or more method names
326 | * @param string $message display output message when the test passes
327 | *
328 | * @return boolean
329 | */
330 | public function can_ok($object, $methods, $message = '')
331 | {
332 | $result = true;
333 | $failed_messages = array();
334 | foreach ((array)$methods as $method) {
335 | if (!method_exists($object, $method)) {
336 | $failed_messages[] = sprintf(" method '%s' does not exist", $method);
337 | $result = false;
338 | }
339 | }
340 |
341 | !$this->ok($result, $message);
342 |
343 | !$result and $this->set_last_test_errors($failed_messages);
344 |
345 | return $result;
346 | }
347 |
348 | /**
349 | * Checks the type of an argument
350 | *
351 | * @param mixed $var variable instance
352 | * @param string $class class or type name
353 | * @param string $message display output message when the test passes
354 | *
355 | * @return boolean
356 | */
357 | public function isa_ok($var, $class, $message = '')
358 | {
359 | $type = is_object($var) ? get_class($var) : gettype($var);
360 | if (!$result = $this->ok($type == $class, $message)) {
361 | $this->set_last_test_errors(array(sprintf(" variable isn't a '%s' it's a '%s'", $class, $type)));
362 | }
363 |
364 | return $result;
365 | }
366 |
367 | /**
368 | * Checks that two arrays have the same values
369 | *
370 | * @param mixed $exp1 first variable
371 | * @param mixed $exp2 second variable
372 | * @param string $message display output message when the test passes
373 | *
374 | * @return boolean
375 | */
376 | public function is_deeply($exp1, $exp2, $message = '')
377 | {
378 | if (!$result = $this->ok($this->test_is_deeply($exp1, $exp2), $message)) {
379 | $this->set_last_test_errors(array(
380 | sprintf(" got: %s", str_replace("\n", '', var_export($exp1, true))),
381 | sprintf(" expected: %s", str_replace("\n", '', var_export($exp2, true)))
382 | ));
383 | }
384 |
385 | return $result;
386 | }
387 |
388 | /**
389 | * is_ignore_nl function.
390 | *
391 | * @access public
392 | * @param mixed $exp1
393 | * @param mixed $exp2
394 | * @param string $message (default: '')
395 | * @return boolean
396 | */
397 | public function is_ignore_nl($exp1, $exp2, $message = '')
398 | {
399 | return $this->is(
400 | str_replace("\n", '', $exp1),
401 | str_replace("\n", '', $exp2),
402 | $message
403 | );
404 | }
405 |
406 | /**
407 | * Sortcut for {@link is}(), performs a strict check.
408 | *
409 | * @access public
410 | * @param mixed $exp1
411 | * @param mixed $exp2
412 | * @param string $message (default: '')
413 | * @return boolean
414 | */
415 | public function is_strict($exp1, $exp2, $message = '')
416 | {
417 | if (is_float($exp1) && is_float($exp2)) {
418 | $value = abs($exp1 - $exp2) < self::EPSILON;
419 | } else {
420 | $value = $exp1 === $exp2;
421 | }
422 |
423 | if (!$result = $this->ok($value, $message)) {
424 | $this->set_last_test_errors(array(
425 | sprintf(" got: (%s) %s", gettype($exp1), var_export($exp1, true)),
426 | sprintf(" expected: (%s) %s", gettype($exp2), var_export($exp2, true))
427 | ));
428 | }
429 |
430 | return $result;
431 | }
432 |
433 | /**
434 | * Sortcut for {@link isnt}(), performs a strict check.
435 | *
436 | * @access public
437 | * @param mixed $exp1
438 | * @param mixed $exp2
439 | * @param string $message . (default: '')
440 | * @return boolean
441 | */
442 | public function isnt_strict($exp1, $exp2, $message = '')
443 | {
444 | if (!$result = $this->ok($exp1 !== $exp2, $message)) {
445 | $this->set_last_test_errors(array(
446 | sprintf(" (%s) %s", gettype($exp1), var_export($exp1, true)),
447 | ' ne',
448 | sprintf(" (%s) %s", gettype($exp2), var_export($exp2, true))
449 | ));
450 | }
451 |
452 | return $result;
453 | }
454 |
455 | /**
456 | * Always passes--useful for testing exceptions
457 | *
458 | * @param string $message display output message
459 | *
460 | * @return boolean
461 | */
462 | public function pass($message = '')
463 | {
464 | return $this->ok(true, $message);
465 | }
466 |
467 | /**
468 | * Always fails--useful for testing exceptions
469 | *
470 | * @param string $message display output message
471 | *
472 | * @return false
473 | */
474 | public function fail($message = '')
475 | {
476 | return $this->ok(false, $message);
477 | }
478 |
479 | /**
480 | * Outputs a diag message but runs no test
481 | *
482 | * @param string $message display output message
483 | *
484 | * @return void
485 | */
486 | public function diag($message)
487 | {
488 | $this->output->diag($message);
489 | }
490 |
491 | /**
492 | * Counts as $nb_tests tests--useful for conditional tests
493 | *
494 | * @param string $message display output message
495 | * @param integer $nb_tests number of tests to skip
496 | *
497 | * @return void
498 | */
499 | public function skip($message = '', $nb_tests = 1)
500 | {
501 | for ($i = 0; $i < $nb_tests; $i++) {
502 | $this->pass(sprintf("# SKIP%s", $message ? ' ' . $message : ''));
503 | $this->results['stats']['skipped'][] = $this->test_nb;
504 | array_pop($this->results['stats']['passed']);
505 | }
506 | }
507 |
508 | /**
509 | * Counts as a test--useful for tests yet to be written
510 | *
511 | * @param string $message display output message
512 | *
513 | * @return void
514 | */
515 | public function todo($message = '')
516 | {
517 | $this->pass(sprintf("# TODO%s", $message ? ' ' . $message : ''));
518 | $this->results['stats']['skipped'][] = $this->test_nb;
519 | array_pop($this->results['stats']['passed']);
520 | }
521 |
522 | /**
523 | * Validates that a file exists and that it is properly included
524 | *
525 | * @param string $file file path
526 | * @param string $message display output message when the test passes
527 | *
528 | * @return boolean
529 | */
530 | public function include_ok($file, $message = '')
531 | {
532 | if (!$result = $this->ok((@include($file)) == 1, $message)) {
533 | $this->set_last_test_errors(array(sprintf(" Tried to include '%s'", $file)));
534 | }
535 |
536 | return $result;
537 | }
538 |
539 | /**
540 | * @param $var1
541 | * @param $var2
542 | * @return bool
543 | */
544 | private function test_is_deeply($var1, $var2)
545 | {
546 | if (gettype($var1) != gettype($var2)) {
547 | return false;
548 | }
549 |
550 | if (is_array($var1)) {
551 | ksort($var1);
552 | ksort($var2);
553 |
554 | $keys1 = array_keys($var1);
555 | $keys2 = array_keys($var2);
556 | if (array_diff($keys1, $keys2) || array_diff($keys2, $keys1)) {
557 | return false;
558 | }
559 | $is_equal = true;
560 | foreach ($var1 as $key => $value) {
561 | $is_equal = $this->test_is_deeply($var1[$key], $var2[$key]);
562 | if ($is_equal === false) {
563 | break;
564 | }
565 | }
566 |
567 | return $is_equal;
568 | } else {
569 | return $var1 === $var2;
570 | }
571 | }
572 |
573 | /**
574 | * @param $message
575 | */
576 | public function comment($message)
577 | {
578 | $this->output->comment($message);
579 | }
580 |
581 | /**
582 | * @param $message
583 | */
584 | public function info($message)
585 | {
586 | $this->output->info($message);
587 | }
588 |
589 | /**
590 | * @param string $message
591 | */
592 | public function error($message, $file = null, $line = null, array $traces = array())
593 | {
594 | $this->output->error($message, $file, $line, $traces);
595 |
596 | $this->results['stats']['errors'][] = array(
597 | 'message' => $message,
598 | 'file' => $file,
599 | 'line' => $line,
600 | );
601 | }
602 |
603 | protected function update_stats()
604 | {
605 | ++$this->test_nb;
606 | ++$this->results['stats']['total'];
607 |
608 | list($this->results['tests'][$this->test_nb]['file'], $this->results['tests'][$this->test_nb]['line']) = $this->find_caller(debug_backtrace());
609 | }
610 |
611 | /**
612 | * @param array $errors
613 | */
614 | protected function set_last_test_errors(array $errors)
615 | {
616 | $this->output->diag($errors);
617 |
618 | $this->results['tests'][$this->test_nb]['error'] = implode("\n", $errors);
619 | }
620 |
621 | /**
622 | * @param $traces
623 | * @return array
624 | */
625 | protected function find_caller($traces)
626 | {
627 | // find the first call to a method of an object that is an instance of lime_test
628 | $t = array_reverse($traces);
629 | foreach ($t as $trace) {
630 | if (isset($trace['object']) && $trace['object'] instanceof lime_test) {
631 | return array($trace['file'], $trace['line']);
632 | }
633 | }
634 |
635 | // return the first call
636 | $last = count($traces) - 1;
637 |
638 | return array($traces[$last]['file'], $traces[$last]['line']);
639 | }
640 |
641 | /**
642 | * @param $code
643 | * @param $message
644 | * @param $file
645 | * @param $line
646 | * @param $context
647 | * @return bool
648 | *
649 | * @FIXME: This method has inconsistent return types
650 | */
651 | public function handle_error($code, $message, $file, $line, $context)
652 | {
653 | if (!$this->options['error_reporting'] || ($code & error_reporting()) == 0) {
654 | return false;
655 | }
656 |
657 | switch ($code) {
658 | case E_WARNING:
659 | $type = 'Warning';
660 | break;
661 | default:
662 | $type = 'Notice';
663 | break;
664 | }
665 |
666 | $trace = debug_backtrace();
667 | array_shift($trace); // remove the handle_error() call from the trace
668 |
669 | $this->error($type . ': ' . $message, $file, $line, $trace);
670 | }
671 |
672 | /**
673 | * @param Exception $exception
674 | * @return bool
675 | */
676 | public function handle_exception(Exception $exception)
677 | {
678 | $this->error(get_class($exception) . ': ' . $exception->getMessage(), $exception->getFile(),
679 | $exception->getLine(), $exception->getTrace());
680 |
681 | // exception was handled
682 | return true;
683 | }
684 | }
--------------------------------------------------------------------------------
/Init.php:
--------------------------------------------------------------------------------
1 | .
18 |
19 | Peachy is not responsible for any damage caused to the system running it.
20 | */
21 |
22 | /**
23 | * @file
24 | * Main Peachy file
25 | * Defines constants, initializes global variables
26 | * Stores Peachy
27 | * @license GPL
28 | */
29 |
30 | /**
31 | * The version that Peachy is running
32 | */
33 | define( 'PEACHYVERSION', '2.0 (alpha 8)' );
34 |
35 | /**
36 | * Minimum MediaWiki version that is required for Peachy
37 | */
38 | define( 'MINMW', '1.23' );
39 |
40 | /**
41 | * Minimum PHP version that is required for Peachy
42 | */
43 | define( 'MINPHP', '5.2.1' );
44 |
45 | /**
46 | * PECHO constants, used for {@link outputText}()
47 | */
48 | define( 'PECHO_VERBOSE', -1 );
49 |
50 | /**
51 | * PECHO constants, used for {@link outputText}()
52 | */
53 | define( 'PECHO_NORMAL', 0 );
54 |
55 | /**
56 | * PECHO constants, used for {@link outputText}()
57 | */
58 | define( 'PECHO_NOTICE', 1 );
59 |
60 | /**
61 | * PECHO constants, used for {@link outputText}()
62 | */
63 | define( 'PECHO_WARN', 2 );
64 |
65 | /**
66 | * PECHO constants, used for {@link outputText}()
67 | */
68 | define( 'PECHO_ERROR', 3 );
69 |
70 | /**
71 | * PECHO constants, used for {@link outputText}()
72 | */
73 | define( 'PECHO_FATAL', 4 );
74 |
75 | $pgIP = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
76 |
77 | //If out /tmp directory doesnt exist, make it!
78 | if( !file_exists( __DIR__ . '/tmp' ) ) {
79 | mkdir(__DIR__ . '/tmp');
80 | }
81 |
82 | require_once( $pgIP . 'Includes/Exceptions.php' );
83 | require_once( $pgIP . 'Includes/AutoUpdate.php' );
84 |
85 | peachyCheckPHPVersion( MINPHP );
86 |
87 | $pgHooks = array();
88 | $pgProxy = array();
89 | $pgUA = 'Peachy MediaWiki Bot API Version ' . PEACHYVERSION;
90 |
91 | require_once( $pgIP . 'Includes/Hooks.php' );
92 | require_once( $pgIP . 'HTTP.php' );
93 | require_once( $pgIP . 'Includes/Autoloader.php' );
94 | require_once( $pgIP . 'GenFunctions.php' );
95 | require_once( $pgIP . 'config.inc.php' );
96 | require_once( $pgIP . 'Includes/SSH.php' );
97 | require_once($pgIP . 'Includes/lime.php');
98 |
99 | $pgVerbose = array();
100 | if( $pgDisplayPechoVerbose ) $pgVerbose[] = -1;
101 | if( $pgDisplayPechoNormal ) $pgVerbose[] = 0;
102 | if( $pgDisplayPechoNotice ) $pgVerbose[] = 1;
103 | if( $pgDisplayPechoWarn ) $pgVerbose[] = 2;
104 | if( $pgDisplayPechoError ) $pgVerbose[] = 3;
105 | if( $pgDisplayPechoFatal ) $pgVerbose[] = 4;
106 |
107 | $pgIRCTrigger = array( '!', '.' );
108 |
109 | //Last version check
110 | $tmp = null;
111 |
112 | if( function_exists( 'mb_internal_encoding' ) ) {
113 | mb_internal_encoding("UTF-8");
114 | }
115 |
116 | // Suppress warnings if timezone not set on server
117 | date_default_timezone_set( @date_default_timezone_get() );
118 |
119 | //Check for updates before loading Peachy.
120 | if( !$pgDisableUpdates && !defined( 'PEACHY_PHPUNIT_TESTS' ) ) {
121 | //the below MUST have its own Http object or else things will break
122 | $updater = new AutoUpdate(new HTTP());
123 | $Uptodate = $updater->Checkforupdate();
124 | if (!$Uptodate) {
125 | $updater->updatePeachy();
126 | }
127 | }
128 |
129 | require_once( $pgIP . 'Includes/Peachy.php' );
130 |
131 | /**
132 | * Simple version_compare() wrapper
133 | * @param string $check_version string
134 | * @throws DependencyError
135 | * @return void
136 | */
137 | function peachyCheckPHPVersion( $check_version )
138 | {
139 | if (version_compare(PHP_VERSION, $check_version, '<')) {
140 | throw new DependencyError("PHP " . $check_version, "http://php.net/downloads.php");
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Plugins/AbuseFilter.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class AbuseFilter {
21 |
22 | /**
23 | * Wiki class
24 | *
25 | * @var Wiki
26 | * @access private
27 | */
28 | private $wiki;
29 |
30 | /**
31 | * Construction method for the AbuseFilter class
32 | *
33 | * @access public
34 | * @param Wiki &$wikiClass The Wiki class object
35 | * @throws DependencyError
36 | */
37 | public function __construct(Wiki &$wikiClass)
38 | {
39 | $this->wiki = $wikiClass;
40 |
41 | if( !array_key_exists( 'Abuse Filter', $wikiClass->get_extensions() ) ) {
42 | throw new DependencyError( "AbuseFilter", "http://www.mediawiki.org/wiki/Extension:AbuseFilter" );
43 | }
44 | }
45 |
46 | /**
47 | * Returns the abuse filter log
48 | *
49 | * @access public
50 | * @param int $filter Filter ID. Default null
51 | * @param string $user Show entries by this user. Default null
52 | * @param string $title Show entries to this title. Default null
53 | * @param int $limit Number of entries to retrieve. Defautl null
54 | * @param string $start Timestamp to start at. Default null
55 | * @param string $end Timestamp to end at. Default null
56 | * @param string $dir Direction to list. Default older
57 | * @param array $prop Properties to retrieve. Default array( 'ids', 'filter', 'user', 'ip', 'title', 'action', 'details', 'result', 'timestamp' )
58 | * @return array
59 | */
60 | public function abuselog( $filter = null, $user = null, $title = null, $limit = null, $start = null, $end = null, $dir = 'older', $prop = array(
61 | 'ids', 'filter', 'user', 'ip', 'title', 'action', 'details', 'result', 'timestamp'
62 | ) ) {
63 |
64 | $tArray = array(
65 | 'prop' => $prop,
66 | '_code' => 'afl',
67 | 'afldir' => $dir,
68 | '_limit' => $limit,
69 | 'list' => 'abuselog',
70 | );
71 |
72 | if( !is_null( $filter ) ) $tArray['aflfilter'] = $filter;
73 | if( !is_null( $user ) ) $tArray['afluser'] = $user;
74 | if( !is_null( $title ) ) $tArray['afltitle'] = $title;
75 | if( !is_null( $start ) ) $tArray['aflstart'] = $start;
76 | if( !is_null( $end ) ) $tArray['aflend'] = $end;
77 |
78 | pecho( "Getting abuse log...\n\n", PECHO_NORMAL );
79 |
80 | return $this->wiki->listHandler( $tArray );
81 | }
82 |
83 | /**
84 | * Returns a list of all filters
85 | *
86 | * @access public
87 | * @param int $start Filter ID to start at. Default null
88 | * @param int $end Filter ID to end at. Default null
89 | * @param string $dir Direction to list. Default newer
90 | * @param bool $enabled Only list enabled filters. true => only enabled, false => only disabled, null => all
91 | * @param bool $deleted Only list deleted filters. true => only deleted, false => only non-deleted, null => all
92 | * @param bool $private Only list private filters. true => only private, false => only non-private, null => all
93 | * @param int $limit Number of filters to get. Default null
94 | * @param array $prop Properties to retrieve. Default array( 'id', 'description', 'pattern', 'actions', 'hits', 'comments', 'lasteditor', 'lastedittime', 'status', 'private' )
95 | * @return array
96 | */
97 | public function abusefilters( $start = null, $end = null, $dir = 'newer', $enabled = null, $deleted = false, $private = null, $limit = null, $prop = array(
98 | 'id', 'description', 'pattern', 'actions', 'hits', 'comments', 'lasteditor', 'lastedittime', 'status', 'private'
99 | ) ) {
100 |
101 | $tArray = array(
102 | 'prop' => $prop,
103 | '_code' => 'abf',
104 | 'abfdir' => $dir,
105 | '_limit' => $limit,
106 | 'abfshow' => array(),
107 | 'list' => 'abusefilters'
108 | );
109 |
110 | if( !is_null( $enabled ) ) {
111 | if( $enabled ) {
112 | $tArray['abfshow'][] = 'enabled';
113 | } else {
114 | $tArray['abfshow'][] = '!enabled';
115 | }
116 | }
117 |
118 | if( !is_null( $deleted ) ) {
119 | if( $deleted ) {
120 | $tArray['abfshow'][] = 'deleted';
121 | } else {
122 | $tArray['abfshow'][] = '!deleted';
123 | }
124 | }
125 |
126 | if( !is_null( $private ) ) {
127 | if( $private ) {
128 | $tArray['abfshow'][] = 'private';
129 | } else {
130 | $tArray['abfshow'][] = '!private';
131 | }
132 | }
133 |
134 | $tArray['abfshow'] = implode( '|', $tArray['abfshow'] );
135 |
136 | if( !is_null( $start ) ) $tArray['abfstartid'] = $start;
137 | if( !is_null( $end ) ) $tArray['abfendid'] = $end;
138 |
139 | pecho( "Getting abuse filter list...\n\n", PECHO_NORMAL );
140 |
141 | return $this->wiki->listHandler( $tArray );
142 | }
143 |
144 |
145 | }
--------------------------------------------------------------------------------
/Plugins/CheckUser.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | class CheckUser {
23 |
24 | public function __construct(Wiki &$wikiclass = null)
25 | {
26 |
27 | $extensions = $wikiClass->get_extensions();
28 | if( !array_key_exists( 'CheckUser', $extensions ) ) {
29 | throw new DependencyError( "CheckUser", "http://www.mediawiki.org/wiki/Extension:CheckUser" );
30 | } elseif( $extensions['CheckUser'] < 3 ) {
31 | throw new DependencyError( "CheckUser version 3.0 or up", "http://www.mediawiki.org/wiki/Extension:CheckUser" );
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/Plugins/CodeReview.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class CodeReview {
21 |
22 | private $wiki;
23 | private $repo;
24 |
25 | public function __construct(Wiki &$wikiClass, $repo)
26 | {
27 | $this->wiki = $wikiClass;
28 | $this->repo = $repo;
29 |
30 | if( !array_key_exists( 'CodeReview', $wikiClass->get_extensions() ) ) {
31 | throw new DependencyError( "CodeReview", "http://www.mediawiki.org/wiki/Extension:CodeReview" );
32 | }
33 | }
34 |
35 | public function update( $rev ) {
36 |
37 | Hooks::runHook( 'StartCodeReviewUpdate', array( &$rev ) );
38 |
39 | $apiRes = $this->wiki->apiQuery(
40 | array(
41 | 'action' => 'codeupdate',
42 | 'repo' => $this->repo,
43 | 'rev' => $rev
44 | ), true
45 | );
46 |
47 | print_r( $apiRes );
48 | }
49 |
50 | public function diff() { }
51 |
52 | public function testupload() { }
53 |
54 | public function comments() { }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/Plugins/Email.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class Email {
21 | /**
22 | * Who to send to
23 | * @var array
24 | */
25 | private $mTo = array();
26 |
27 | /**
28 | * Who the message is from
29 | * @var array
30 | */
31 | private $mFrom = array();
32 |
33 | /**
34 | * Subject
35 | * @var string
36 | */
37 | private $mSubject;
38 |
39 | /**
40 | * Message to send
41 | * @var string
42 | */
43 | private $mMessage;
44 |
45 | /**
46 | * CC field
47 | * @var array
48 | */
49 | private $mCC = array();
50 |
51 | /**
52 | * BCC field
53 | * @var array
54 | */
55 | private $mBCC = array();
56 |
57 | /**
58 | * Reply-to field
59 | * @var array
60 | */
61 | private $mRT = array();
62 |
63 | /**
64 | * Construct function, adds the From: field, subject, and message
65 | * @param string $fromEmail Email address of sender
66 | * @param string $fromName Name of sender.
67 | * @param string $subject Subject of email
68 | * @param string $message Message to send
69 | * @throws DependencyError
70 | */
71 | function __construct( $fromEmail, $fromName, $subject, $message ) {
72 |
73 | if( !function_exists( 'mail' ) ) {
74 | throw new DependencyError( "Mail", "http://us4.php.net/manual/en/book.mail.php" );
75 | }
76 |
77 | if( !is_null( $fromName ) ) {
78 | $this->mFrom[$fromName] = $fromEmail;
79 | } else {
80 | $this->mFrom[] = $fromEmail;
81 | }
82 | $this->mSubject = $subject;
83 | $this->mMessage = $message;
84 | }
85 |
86 | /**
87 | * Adds another email to the To: field.
88 | * @param string $toEmail Email address of recipient
89 | * @param string $toName Name of recipient. Default null
90 | */
91 | public function addTarget( $toEmail, $toName = null ) {
92 | if( !is_null( $toName ) ) {
93 | $this->mTo[$toName] = $toEmail;
94 | } else {
95 | $this->mTo[] = $toEmail;
96 | }
97 | }
98 |
99 | /**
100 | * Adds another email to the CC: field.
101 | * @param string $ccEmail Email address of cc
102 | * @param string $ccName Name of cc. Default null
103 | */
104 | public function addCC( $ccEmail, $ccName = null ) {
105 | if( !is_null( $ccName ) ) {
106 | $this->mCC[$ccName] = $ccEmail;
107 | } else {
108 | $this->mCC[] = $ccEmail;
109 | }
110 | }
111 |
112 | /**
113 | * Adds another email to the BCC: field.
114 | * @param string $bccEmail Email address of bcc
115 | * @param string $bccName Name of bcc. Default null
116 | */
117 | public function addBCC( $bccEmail, $bccName = null ) {
118 | if( !is_null( $bccName ) ) {
119 | $this->mBCC[$bccName] = $bccEmail;
120 | } else {
121 | $this->mBCC[] = $bccEmail;
122 | }
123 | }
124 |
125 | /**
126 | * Adds another email to the Reply-to: field.
127 | * @param string $rtEmail Email address to reply to
128 | * @param string $rtName Name to reply to. Default null
129 | */
130 | public function addReplyTo( $rtEmail, $rtName = null ) {
131 | if( !is_null( $rtName ) ) {
132 | $this->mRT[$rtName] = $rtEmail;
133 | } else {
134 | $this->mRT[] = $rtEmail;
135 | }
136 | }
137 |
138 | /**
139 | * Sends the email.
140 | */
141 | public function send() {
142 | $msg = array();
143 |
144 | $msg_to = array();
145 | foreach( $this->mTo as $name => $target ){
146 | if( !is_string( $name ) ) {
147 | $msg_to[] = $target;
148 | } else {
149 | $msg_to[] = "$name <$target>";
150 | }
151 | }
152 | $msg['to'] = implode( ', ', $msg_to );
153 |
154 | $msg_from = array();
155 | foreach( $this->mFrom as $name => $target ){
156 | if( !is_string( $name ) ) {
157 | $msg_from[] = $target;
158 | } else {
159 | $msg_from[] = "$name <$target>";
160 | }
161 | }
162 | $msg['from'] = null;
163 | if( count( $msg_from ) > 0 ) $msg['from'] = "From: " . implode( ', ', $msg_from );
164 |
165 | $msg_cc = array();
166 | foreach( $this->mCC as $name => $target ){
167 | if( !is_string( $name ) ) {
168 | $msg_cc[] = $target;
169 | } else {
170 | $msg_cc[] = "$name <$target>";
171 | }
172 | }
173 | $msg['cc'] = null;
174 | if( count( $msg_cc ) > 0 ) $msg['cc'] = "CC: " . implode( ', ', $msg_cc );
175 |
176 | $msg_bcc = array();
177 | foreach( $this->mBCC as $name => $target ){
178 | if( !is_string( $name ) ) {
179 | $msg_bcc[] = $target;
180 | } else {
181 | $msg_bcc[] = "$name";
182 | }
183 | }
184 | $msg['bcc'] = null;
185 | if( count( $msg_bcc ) > 0 ) $msg['bcc'] = "BCC: " . implode( ', ', $msg_bcc );
186 |
187 | $msg_rt = array();
188 | foreach( $this->mRT as $name => $target ){
189 | if( !is_string( $name ) ) {
190 | $msg_rt[] = $target;
191 | } else {
192 | $msg_rt[] = "$name <$target>";
193 | }
194 | }
195 | $msg['rt'] = null;
196 | if( count( $msg_rt ) > 0 ) $msg['rt'] = "Reply-to: " . implode( ', ', $msg_rt );
197 |
198 | $msg['subject'] = $this->mSubject;
199 | $msg['message'] = $this->mMessage;
200 | $msg['version'] = 'X-Mailer: PHP/' . phpversion();
201 |
202 | $msg['headers'] = $msg['from'] . "\r\n";
203 | if( !is_null( $msg['rt'] ) ) $msg['headers'] .= $msg['rt'] . "\r\n";
204 | if( !is_null( $msg['cc'] ) ) $msg['headers'] .= $msg['cc'] . "\r\n";
205 | if( !is_null( $msg['bcc'] ) ) $msg['headers'] .= $msg['bcc'] . "\r\n";
206 | $msg['headers'] .= $msg['version'];
207 |
208 | $result = mail( $msg['to'], $msg['subject'], $msg['message'], $msg['headers'] );
209 |
210 | return $result;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/Plugins/FlaggedRevs.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class FlaggedRevs {
21 |
22 | private $wiki;
23 |
24 | /**
25 | * @param Wiki $wikiClass
26 | * @throws DependencyError
27 | */
28 | function __construct( Wiki &$wikiClass ) {
29 |
30 | if( !array_key_exists( 'FlaggedRevs', $wikiClass->get_extensions() ) ) {
31 | throw new DependencyError( "FlaggedRevs", "http://www.mediawiki.org/wiki/Extension:FlaggedRevs" );
32 | }
33 |
34 | $this->wiki = $wikiClass;
35 | }
36 |
37 | /**
38 | * @param $revid
39 | * @param null $reason
40 | * @param int $status
41 | * @return bool
42 | * @throws AssertFailure
43 | * @throws BadEntryError
44 | * @throws HookError
45 | * @throws LoggedOut
46 | * @throws MWAPIError
47 | */
48 | public function review( $revid, $reason = null, $status = 1 ) {
49 |
50 | if( !in_array( 'review', $this->wiki->get_userrights() ) ) {
51 | pecho( "User is not allowed to review revisions", PECHO_FATAL );
52 | return false;
53 | }
54 |
55 | $tokens = $this->wiki->get_tokens();
56 |
57 | if( $tokens['edit'] == '+\\' ) {
58 | pecho( "User has logged out.\n\n", PECHO_FATAL );
59 | return false;
60 | }
61 |
62 | if( mb_strlen( $reason, '8bit' ) > 255 ) {
63 | pecho( "Comment is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
64 | return false;
65 | }
66 |
67 | pecho( "Reviewing $revid...\n\n", PECHO_NOTICE );
68 |
69 | $editarray = array(
70 | 'flag_accuracy' => $status,
71 | 'action' => 'review',
72 | 'token' => $tokens['edit'],
73 | 'revid' => $revid
74 | );
75 |
76 | if( !empty( $reason ) ) $editArray['comment'] = $reason;
77 |
78 | if( $this->wiki->get_maxlag() ) {
79 | $editarray['maxlag'] = $this->wiki->get_maxlag();
80 | }
81 |
82 | Hooks::runHook( 'StartReview', array( &$editarray ) );
83 |
84 | $result = $this->wiki->apiQuery( $editarray, true );
85 |
86 | if( isset( $result['review'] ) ) {
87 | if( isset( $result['review']['revid'] ) ) {
88 | return true;
89 | } else {
90 | pecho( "Review error...\n\n" . print_r( $result['review'], true ) . "\n\n", PECHO_FATAL );
91 | return false;
92 | }
93 | } else {
94 | pecho( "Review error...\n\n" . print_r( $result, true ), PECHO_FATAL );
95 | return false;
96 | }
97 | }
98 |
99 | /**
100 | * @param $title
101 | * @param string $level
102 | * @param null $reason
103 | * @param bool|false $autoreview
104 | * @param bool|false $watch
105 | * @return bool
106 | * @throws AssertFailure
107 | * @throws BadEntryError
108 | * @throws HookError
109 | * @throws LoggedOut
110 | * @throws MWAPIError
111 | */
112 | public function stabilize( $title, $level = 'none', $reason = null, $autoreview = false, $watch = false ) {
113 |
114 | if( !in_array( 'stablesettings', $this->wiki->get_userrights() ) ) {
115 | pecho( "User is not allowed to change the stabilization settings", PECHO_FATAL );
116 | return false;
117 | }
118 |
119 | $tokens = $this->wiki->get_tokens();
120 |
121 | if( $tokens['edit'] == '+\\' ) {
122 | pecho( "User has logged out.\n\n", PECHO_FATAL );
123 | return false;
124 | }
125 |
126 | if( mb_strlen( $reason, '8bit' ) > 255 ) {
127 | pecho( "Comment is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
128 | return false;
129 | }
130 |
131 | pecho( "Stabilizing title...\n\n", PECHO_NOTICE );
132 |
133 | $editarray = array(
134 | 'action' => 'review',
135 | 'title' => $title,
136 | 'token' => $tokens['edit'],
137 | 'protectlevel' => $level
138 | );
139 |
140 | if( $watch ) $editArray['watch'] = 'yes';
141 | if( !empty( $reason ) ) $editArray['reason'] = $reason;
142 |
143 | if( $this->wiki->get_maxlag() ) {
144 | $editarray['maxlag'] = $this->wiki->get_maxlag();
145 | }
146 |
147 | Hooks::runHook( 'StartStabilize', array( &$editarray ) );
148 |
149 | $result = $this->wiki->apiQuery( $editarray, true );
150 |
151 | if( isset( $result['stabilize'] ) ) {
152 | if( isset( $result['stabilize']['title'] ) ) {
153 | return true;
154 | } else {
155 | pecho( "Stabilization error...\n\n" . print_r( $result['stabilize'], true ) . "\n\n", PECHO_FATAL );
156 | return false;
157 | }
158 | } else {
159 | pecho( "Stabilization error...\n\n" . print_r( $result, true ), PECHO_FATAL );
160 | return false;
161 | }
162 |
163 | }
164 |
165 | public function flagconfig() { }
166 |
167 | public function reviewedpages() { }
168 |
169 | public function unreviewedpages() { }
170 |
171 | public function oldreviewedpages() { }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/Plugins/GlobalBlocking.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class GlobalBlocking {
21 |
22 | public static function load( Wiki &$wikiClass ) {
23 |
24 | if( !array_key_exists( 'GlobalBlocking', $wikiClass->get_extensions() ) ) {
25 | throw new DependencyError( "GlobalBlocking", "http://www.mediawiki.org/wiki/Extension:GlobalBlocking" );
26 | }
27 |
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/Plugins/GlobalUserInfo.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class GlobalUserInfo {
21 |
22 | /**
23 | * Wiki class
24 | *
25 | * @var Wiki
26 | * @access private
27 | */
28 | private $wiki;
29 |
30 | /**
31 | * Username
32 | *
33 | * @var string
34 | * @access private
35 | */
36 | private $pgUsername;
37 |
38 | /**
39 | * Global groups member is a part of
40 | *
41 | * @var array
42 | * @access private
43 | */
44 | private $groups = array();
45 |
46 | /**
47 | * Accounts that user has merged on other wikis
48 | *
49 | * @var array
50 | * @access private
51 | */
52 | private $merged = array();
53 |
54 | /**
55 | * Accounts that are not attached to the global account
56 | *
57 | * @var array
58 | * @access private
59 | */
60 | private $unattached = array();
61 |
62 | /**
63 | * Whether or not global account exists
64 | *
65 | * @var bool
66 | * @access private
67 | */
68 | private $exists = true;
69 |
70 | /**
71 | * Date that global account was created
72 | *
73 | * @var string
74 | * @access private
75 | */
76 | private $registration;
77 |
78 | /**
79 | * Global account ID
80 | *
81 | * @var int
82 | * @access private
83 | */
84 | private $id;
85 |
86 | /**
87 | * Construction method for the GlobalUserInfo class
88 | *
89 | * @access public
90 | * @param Wiki &$wikiClass The Wiki class object
91 | * @param mixed $pgUsername Username
92 | * @throws APIError
93 | * @throws AssertFailure
94 | * @throws DependencyError
95 | * @throws LoggedOut
96 | * @throws MWAPIError
97 | */
98 | function __construct( Wiki &$wikiClass, $pgUsername ) {
99 |
100 | if( !array_key_exists( 'Central Auth', $wikiClass->get_extensions() ) ) {
101 | throw new DependencyError( "CentralAuth", "http://www.mediawiki.org/wiki/Extension:CentralAuth" );
102 | }
103 |
104 | $this->username = ucfirst( $pgUsername );
105 | $this->wiki = $wikiClass;
106 |
107 | $guiRes = $this->wiki->apiQuery(
108 | array(
109 | 'action' => 'query',
110 | 'meta' => 'globaluserinfo',
111 | 'guiuser' => ucfirst( $pgUsername ),
112 | 'guiprop' => 'groups|merged|unattached',
113 | ), false, false
114 | );
115 |
116 | if( !isset( $guiRes['query']['globaluserinfo'] ) ) {
117 | $this->exists = false;
118 | if( isset( $guiRes['error'] ) && $guiRes['error']['code'] != 'guinosuchuser' ) {
119 | throw new MWAPIError($guiRes['error']);
120 | } elseif( @$guiRes['error']['code'] != 'guinosuchuser' ) {
121 | throw new MWAPIError(array('code' => 'UnknownError', 'info' => 'Unknown API Error'));
122 | }
123 | } else {
124 | $this->groups = $guiRes['query']['globaluserinfo']['groups'];
125 | $this->merged = $guiRes['query']['globaluserinfo']['merged'];
126 | $this->merged = $guiRes['query']['globaluserinfo']['unattached'];
127 | $this->id = $guiRes['query']['globaluserinfo']['id'];
128 | $this->registration = $guiRes['query']['globaluserinfo']['registration'];
129 | }
130 | }
131 |
132 | /**
133 | * Returns the global account ID
134 | *
135 | * @return int
136 | * @access public
137 | */
138 | public function get_id() {
139 | return $this->id;
140 | }
141 |
142 | /**
143 | * Returns the date that global account was created
144 | *
145 | * @return string
146 | * @access public
147 | */
148 | public function get_registration() {
149 | return $this->registration;
150 | }
151 |
152 | /**
153 | * Returns the global groups member is a part of
154 | *
155 | * @return array
156 | * @access public
157 | */
158 | public function get_groups() {
159 | return $this->groups;
160 | }
161 |
162 | /**
163 | * Returns the accounts that user has merged on other wikis
164 | *
165 | * @return array
166 | * @access public
167 | */
168 | public function get_merged() {
169 | return $this->merged;
170 | }
171 |
172 | /**
173 | * Returns the accounts that are not attached to the global account
174 | *
175 | * @return array
176 | * @access public
177 | */
178 | public function get_unattached() {
179 | return $this->unattached;
180 | }
181 |
182 | /**
183 | * Returns whether or not global account exists
184 | *
185 | * @return bool
186 | * @access public
187 | */
188 | public function get_exists() {
189 | return $this->unattached;
190 | }
191 |
192 | }
--------------------------------------------------------------------------------
/Plugins/IRC.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class IRC {
21 |
22 | /**
23 | * IRC Socket connection
24 | *
25 | * @var object
26 | * @access public
27 | */
28 | public $f;
29 |
30 | /**
31 | * Channel(s)
32 | *
33 | * @var string|array
34 | * @access private
35 | */
36 | private $chan;
37 |
38 | /**
39 | * Construct function, front-end for fsockopen.
40 | * @param string $User Username to send to IRC
41 | * @param string $Nick Nick to use
42 | * @param string $Pass Password to send
43 | * @param string $Server Server to connect to
44 | * @param string $pgPort Port to use
45 | * @param string $Gecos AKA Real Name, Information field, etc.
46 | * @param string|array Channel (s) to connect to
47 | */
48 | function __construct( $User, $Nick, $Pass, $Server, $pgPort, $Gecos, $Channel ) {
49 | $this->f = fsockopen( $Server, $pgPort, $errno, $errstr, 30 );
50 |
51 | if( !$this->f ) {
52 | die( $errstr . ' (' . $errno . ")\n" );
53 | }
54 |
55 | pecho( "Logging into IRC as $User into $Server:$pgPort\n\n", PECHO_NOTICE );
56 |
57 | $this->sendToIrc( 'USER ' . $User . ' "' . $Server . '" "localhost" :' . $Gecos . "\n" );
58 | $this->sendToIrc( 'PASS ' . $Pass . "\n" );
59 | $this->sendToIrc( 'NICK ' . $Nick . "\n" );
60 |
61 | if( !is_array( $Channel ) ) {
62 | $this->chan = array( $Channel );
63 | } else {
64 | $this->chan = $Channel;
65 | }
66 | $this->joinChan( $Channel );
67 | }
68 |
69 | /**
70 | * Destruct function, quits from IRC
71 | * @return void
72 | */
73 | public function quit() {
74 | fwrite( $this->f, 'QUIT ' . "\n" );
75 | }
76 |
77 | /**
78 | * Sends a raw message to IRC
79 | * @param string $msg Message to send
80 | * @return void
81 | */
82 | public function sendToIrc( $msg ) {
83 | fwrite( $this->f, $msg );
84 | }
85 |
86 | /**
87 | * Send a message to a channel, formatted in PRIVMSG format
88 | * @param string $msg Message to send
89 | * @param string $chan Channel to send to
90 | * @return void
91 | */
92 | public function sendPrivmsg( $msg, $chan ) {
93 | pecho( "Sending $msg to $chan...\n\n", PECHO_VERBOSE );
94 | fwrite( $this->f, "PRIVMSG " . $chan . " :$msg\n" );
95 | }
96 |
97 | /**
98 | * Return the pingpong game
99 | * @param string $payload Data from the PING message
100 | * @return void
101 | */
102 | public function sendPong( $payload ) {
103 | fwrite( $this->f, "PONG " . $payload . "\r\n" );
104 | }
105 |
106 | /**
107 | * Joins a channel, or the locally stored channel(s)
108 | * @param string $chan Channel to join. Default null.
109 | * @return void
110 | */
111 | public function joinChan( $chan = null ) {
112 | if( !is_null( $chan ) && !is_array( $chan ) ) {
113 | pecho( "Joining $chan...\n", PECHO_VERBOSE );
114 | fwrite( $this->f, 'JOIN ' . $chan . "\n" );
115 | usleep( 5000 );
116 | } elseif( !is_null( $chan ) ) {
117 | foreach( $chan as $channel ){
118 | pecho( "Joining $channel...\n", PECHO_VERBOSE );
119 | fwrite( $this->f, 'JOIN ' . $channel . "\n" );
120 | usleep( 5000 );
121 | }
122 | }
123 | }
124 |
125 | /**
126 | * Leaves a channel, or the locally stored channel(s)
127 | * @param string $chan Channel to part. Default null
128 | * @return void
129 | */
130 | public function partChan( $chan = null ) {
131 | if( !is_null( $chan ) ) {
132 | pecho( "Parting $chan...\n", PECHO_VERBOSE );
133 | fwrite( $this->f, 'PART ' . $chan . "\n" );
134 | usleep( 5000 );
135 | } else {
136 | foreach( $this->chan as $chan ){
137 | pecho( "Parting $chan...\n", PECHO_VERBOSE );
138 | fwrite( $this->f, 'PART ' . $chan . "\n" );
139 | usleep( 5000 );
140 | }
141 | }
142 | }
143 |
144 | /**
145 | * Splits apart the various parts of an IRC line into usable sections, e.g. !commands, cloaks, etc.
146 | * @param string $line Line that IRC sent
147 | * @param array $trigger Trigger character for !commands (e.g. !, ., @, etc)
148 | * @param bool $feed Whether or not the IRC server is a MediaWiki RC channel
149 | * @return array Parsed line
150 | * @static
151 | */
152 | public static function parseLine( $line, $trigger, $feed = false ) {
153 | $return = array();
154 | $return['trueraw'] = $line;
155 | $return['truerawmsg'] = explode( " ", $line );
156 | unset( $return['truerawmsg'][0], $return['truerawmsg'][1], $return['truerawmsg'][2] );
157 | $return['truerawmsg'] = substr( implode( ' ', $return['truerawmsg'] ), 1 );
158 |
159 | if( $feed ) {
160 | $line = str_replace( array( "\n", "\r", "\002" ), '', $line );
161 | $line = preg_replace( '/\003(\d\d?(,\d\d?)?)?/', '', $line );
162 | } else {
163 | $line = str_replace( array( "\n", "\r" ), '', $line );
164 | $line = preg_replace( '/' . chr( 3 ) . '.{2,}/i', '', $line );
165 | }
166 |
167 | $return['raw'] = $line;
168 |
169 | /*
170 | Data for a privmsg:
171 | $d[0] = Nick!User@Host format.
172 | $d[1] = Action, e.g. "PRIVMSG", "MODE", etc. If it's a message from the server, it's the numerial code
173 | $d[2] = The channel somethign was spoken in
174 | $d[3] = The text that was spoken
175 | */
176 | $d = $return['message'] = explode( ' ', $line );
177 | $return['n!u@h'] = $d[0];
178 |
179 | unset( $return['message'][0], $return['message'][1], $return['message'][2] );
180 | $return['message'] = substr( implode( ' ', $return['message'] ), 1 );
181 |
182 | $return['nick'] = substr( $d[0], 1 );
183 | $return['nick'] = explode( '!', $return['nick'] );
184 | $return['nick'] = $return['nick'][0];
185 |
186 | $return['cloak'] = explode( '@', $d[0] );
187 | $return['cloak'] = @$return['cloak'][1];
188 |
189 | $return['user'] = explode( '!', $d[0] );
190 | $return['user'] = explode( '@', $return['user'][1] );
191 | $return['user'] = $return['user'][0];
192 |
193 | $return['chan'] = strtolower( $d[2] );
194 |
195 | $return['type'] = $return['payload'] = $d[1];
196 |
197 | if( in_array( substr( $return['message'], 0, 1 ), $trigger ) ) {
198 | $return['command'] = explode( ' ', substr( strtolower( $return['message'] ), 1 ) );
199 | $return['command'] = $return['command'][0];
200 |
201 | //Get the parameters
202 | $return['param'] = explode( ' ', $return['message'] );
203 | unset( $return['param'][0] );
204 | $return['param'] = implode( ' ', $return['param'] );
205 | $return['param'] = trim( $return['param'] );
206 | }
207 |
208 | /*
209 | End result:
210 | $return['raw'] = Raw data
211 | $return['message'] = The text that appears in the channel
212 | $return['n!u@h'] = The person who said the line, in N!U@H format
213 | $return['nick'] = The nick who said the line
214 | $return['cloak'] = The cloak of the person who said the line
215 | $return['user'] = The username who said the line
216 | $return['chan'] = The channel the line was said in
217 | $return['type'] = The action that was done (eg PRIVMSG, MODE)
218 | $return['payload'] = For pings, this is $d[1]
219 | $return['command'] = The command that was said, eg !status (excuding !)
220 | $return['param'] = Parameters of the command
221 | */
222 | return $return;
223 | }
224 |
225 | /**
226 | * Parses the title, user, etc from a MediaWiki RC feed
227 | * @link http://www.mediawiki.org/wiki/Manual:IRC_RC_Bot
228 | * @param string $msg Message from feed
229 | * @return array Parsed line
230 | * @static
231 | */
232 | public static function parseRC( $msg ) {
233 | if( preg_match( '/^\[\[((Talk|User|Wikipedia|Image|MediaWiki|Template|Help|Category|Portal|Special)(( |_)talk)?:)?([^\x5d]*)\]\] (\S*) (http:\/\/en\.wikipedia\.org\/w\/index\.php\?(oldid|diff)=(\d*)&(rcid|oldid)=(\d*).*|http:\/\/en\.wikipedia\.org\/wiki\/\S+)? \* ([^*]*) \* (\(([^)]*)\))? (.*)$/S', $msg, $m ) ) {
234 |
235 | $return = array();
236 |
237 | //print_r($m);
238 |
239 | $return['namespace'] = $m[2];
240 | $return['pagename'] = $m[5];
241 | $return['fullpagename'] = $m[1] . $m[5];
242 | $return['basepagename'] = explode( '/', $return['fullpagename'] );
243 | $return['basepagename'] = $return['basepagename'][0];
244 | $return['subpagename'] = str_replace( $return['basepagename'] . '/', '', $return['fullpagename'] );
245 | $return['flags'] = str_split( $m[6] );
246 | $return['action'] = $m[6];
247 | $return['url'] = $m[7];
248 | $return['revid'] = $m[9];
249 | $return['oldid'] = $m[11];
250 | $return['username'] = $m[12];
251 | $return['len'] = $m[14];
252 | $return['comment'] = $m[15];
253 | $return['timestamp'] = time();
254 | $return['is_new'] = false;
255 | $return['is_minor'] = false;
256 | $return['is_bot'] = false;
257 | $return['is_delete'] = false;
258 | $return['actionpage'] = null;
259 |
260 | if( in_array( 'N', $return['flags'] ) ) {
261 | $return['is_new'] = true;
262 | }
263 |
264 | if( in_array( 'M', $return['flags'] ) ) {
265 | $return['is_minor'] = true;
266 | }
267 |
268 | if( in_array( 'B', $return['flags'] ) ) {
269 | $return['is_bot'] = true;
270 | }
271 |
272 | if( $return['action'] == 'delete' ) {
273 | $return['is_delete'] = true;
274 | $tmp = explode( '[[', $return['comment'] );
275 | $tmp = explode( ']]', $tmp[1] );
276 | $return['actionpage'] = $tmp[0];
277 | $return['actionpageprefix'] = explode( '/', $return['actionpage'] );
278 | $return['actionpageprefix'] = $return['actionpageprefix'][0];
279 | }
280 |
281 | return $return;
282 | }
283 | }
284 |
285 | /**
286 | * @param $errno
287 | * @return bool|string
288 | */
289 | public static function get_error($errno)
290 | {
291 | if ($errno != null) {
292 | switch ($errno) {
293 | case 401:
294 | return "Nickname/Channel is currently unused";
295 | case 402:
296 | return "Server not found";
297 | case 403:
298 | return "Channel not found";
299 | case 404:
300 | return "Cannot send to channel";
301 | case 405:
302 | return "Too many channels joined";
303 | case 406:
304 | return "There was no such nickname";
305 | }
306 | } else {
307 | return false;
308 | }
309 | }
310 | }
311 |
312 | class SimpleIRC {
313 |
314 | private $server;
315 | private $pgPort;
316 | private $user;
317 | private $pass;
318 | private $nick;
319 | private $channel;
320 | private $callback;
321 |
322 | function __construct( $server, $pgPort = 6667, $user, $pass, $nick, $channel, $callback = null ) {
323 | global $pgIRCTrigger, $pgHooks;
324 |
325 | if( func_num_args() > 6 ) {
326 | $this->server = $server;
327 | $this->port = $pgPort;
328 | $this->user = $user;
329 | $this->pass = $pass;
330 | $this->nick = $nick;
331 | $this->channel = $channel;
332 | $this->callback = $callback;
333 | } else {
334 | $this->server = $server;
335 | $this->port = 6667;
336 | $this->user = $pgPort;
337 | $this->pass = $user;
338 | $this->nick = $pass;
339 | $this->channel = $nick;
340 | $this->callback = $channel;
341 | }
342 |
343 | $pgHooks['SimpleIRCPrivMSG'][] = $callback;
344 |
345 | $irc = new IRC( $this->user, $this->nick, $this->pass, $this->server, $this->port, "Peachy IRC Bot Version " . PEACHYVERSION, $this->channel );
346 |
347 | while( !feof( $irc->f ) ){
348 |
349 | $parsed = IRC::parseLine( fgets( $irc->f, 1024 ), $pgIRCTrigger, true );
350 |
351 | if( @$parsed['n!u@h'] == 'PING' ) {
352 | $irc->sendPong( $parsed['payload'] );
353 | }
354 |
355 | if( @$parsed['type'] == '376' || @$parser['type'] == '422' ) {
356 | $feed->joinChan();
357 | sleep( 5 );
358 | }
359 |
360 | if( @$parsed['type'] == 'PRIVMSG' ) {
361 | Hooks::runHook( 'SimpleIRCPrivMSG', array( &$parsed, &$irc, &$this ) );
362 | }
363 | }
364 | }
365 |
366 | }
367 |
--------------------------------------------------------------------------------
/Plugins/RFA.php:
--------------------------------------------------------------------------------
1 | initPage( $page )->get_text();
42 |
43 | $split = preg_split(
44 | "/^(?:(?:'''|(?:clude><\/includeonly>)?={4,5}(?:<\/noin<\/includeonly>clude><\/includeonly>''')?)"
45 | . "\s*?(Support|Oppose|Neutral|Comments)\s*?(?:'''|(?:'''clude><\/includeonly>)?={4,5}(?:<\/noin<\/includeonly>clude><\/includeonly>)?)|;\s*(Support|Oppose|Neutral|Comments))\s*(?:
|
)?\s*$/im"
46 | , $rawwikitext, -1, PREG_SPLIT_DELIM_CAPTURE
47 | );
48 |
49 | $header = array_shift( $split );
50 |
51 | //=== Deal with the header ===//
52 | $header = str_ireplace( array( '', '' ), '', $header );
53 |
54 | if( preg_match( "/===\s*\[\[User:(.*?)\|.*?\]\]\s*===/", $header, $matches ) ) {
55 | $this->username = $matches[1];
56 | } elseif( preg_match( "/===\s*\[\[.*?\|(.*?)\]\]\s*===/", $header, $matches ) ) {
57 | $this->username = $matches[1];
58 | }
59 |
60 | $header = str_replace( array( '[[', ']]' ), '', $header );
61 |
62 | if( preg_match( "/end(?:ing|ed)?(?: no earlier than)? (.*?) \(UTC\)/i", $header, $matches ) ) {
63 | $this->enddate = $matches[1];
64 | }
65 | //=== End header stuff ===//
66 |
67 | //Now parse through each non-header section, figuring out what they are
68 | //Nothing expected = 0, Support = 1, Oppose = 2, Neutral = 3
69 | $nextsection = 0;
70 |
71 | foreach( $split as $splut ){
72 | $splut = trim( $splut );
73 | if( empty( $splut ) ) {
74 | continue;
75 | }
76 |
77 | if( strcasecmp( $splut, 'Support' ) == 0 ) {
78 | $nextsection = 1;
79 | } elseif( strcasecmp( $splut, 'Oppose' ) == 0 ) {
80 | $nextsection = 2;
81 | } elseif( strcasecmp( $splut, 'Neutral' ) == 0 ) {
82 | $nextsection = 3;
83 | } else {
84 | switch( $nextsection ){
85 | case 1:
86 | $support = $splut;
87 | break;
88 | case 2:
89 | $oppose = $splut;
90 | break;
91 | case 3:
92 | $neutral = $splut;
93 | break;
94 | }
95 | $nextsection = 0;
96 | }
97 | }
98 |
99 | if( !isset( $support ) ) {
100 | $this->lasterror = "Support section not found";
101 | return false;
102 | }
103 | if( !isset( $oppose ) ) {
104 | $this->lasterror = "Oppose section not found";
105 | return false;
106 | }
107 | if( !isset( $neutral ) ) {
108 | $this->lasterror = "Neutral section not found";
109 | return false;
110 | }
111 |
112 | $this->support = $this->analyzeSection( $support );
113 | $this->oppose = $this->analyzeSection( $oppose );
114 | $this->neutral = $this->analyzeSection( $neutral );
115 |
116 | //Merge all votes in one array and sort:
117 | $m = array();
118 | foreach( $this->support as $s ){
119 | if( isset( $s['name'] ) ) $m[] = $s['name'];
120 | }
121 | foreach( $this->oppose as $o ){
122 | if( isset( $o['name'] ) ) $m[] = $o['name'];
123 | }
124 | foreach( $this->neutral as $n ){
125 | if( isset( $n['name'] ) ) $m[] = $n['name'];
126 | }
127 | sort( $m );
128 | //Find duplicates:
129 | for( $i = 0; $i < count( $m ); $i++ ){
130 | if( $i != count( $m ) - 1 ) {
131 | if( $m[$i] == $m[$i + 1] ) {
132 | $this->duplicates[] = $m[$i];
133 | }
134 | }
135 | }
136 |
137 | return true;
138 | }
139 |
140 | public function get_username() {
141 | return $this->username;
142 | }
143 |
144 | public function get_enddate() {
145 | return $this->enddate;
146 | }
147 |
148 | public function get_support() {
149 | return $this->support;
150 | }
151 |
152 | public function get_oppose() {
153 | return $this->oppose;
154 | }
155 |
156 | public function get_neutral() {
157 | return $this->neutral;
158 | }
159 |
160 | public function get_duplicates() {
161 | return $this->duplicates;
162 | }
163 |
164 | public function get_lasterror() {
165 | return $this->lasterror;
166 | }
167 |
168 | /**
169 | * Attempts to find a signature in $input using the default regex. Returns matches.
170 | * @param $input
171 | * @param $matches
172 | *
173 | * @return int
174 | */
175 | protected function findSig( $input, &$matches ) {
176 | //Supports User: and User talk: wikilinks, {{fullurl}}, unsubsted {{unsigned}}, unsubsted {{unsigned2}}, anything that looks like a custom sig template
177 | return preg_match_all(
178 | "/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\|\/]*)(?:\|[^\]]*)?\]\]" //1: Normal [[User:XX]] and [[User talk:XX]]
179 | . "|\{\{(?:[Ff]ullurl\:[Uu]ser(?:[\s_][Tt]alk)?\:|[Uu]nsigned\|)([^\}\|]*)(?:|[\|\}]*)?\}\}" //2: {{fullurl}} and {{unsigned}} templates
180 | . "|(?:\{\{)[Uu]ser(?:[\s_][Tt]alk)?\:([^\}\/\|]*)" //3: {{User:XX/sig}} templates
181 | . "|\{\{[Uu]nsigned2\|[^\|]*\|([^\}]*)\}\}" //4: {{unsigned2|Date|XX}} templates
182 | . "|(?:\[\[)[Uu]ser\:([^\]\/\|]*)\/[Ss]ig[\|\]]/" //5: [[User:XX/sig]] links (compromise measure)
183 | , $input, $matches, PREG_OFFSET_CAPTURE
184 | );
185 | }
186 |
187 | /**
188 | * Attempts to find a signature in $input using a different regex. Returns matches.
189 | * @param $input
190 | * @param $matches
191 | *
192 | * @return int
193 | */
194 | protected function findSigAlt( $input, &$matches ) {
195 | return preg_match_all(
196 | "/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\/\|]*)" //5: "[[User:XX/PageAboutMe" links (notice no end tag)
197 | . "|\[\[[Ss]pecial\:[Cc]ontributions\/([^\|\]]*)/"
198 | , $input, $matches, PREG_OFFSET_CAPTURE
199 | );
200 | }
201 |
202 | /**
203 | * Attempts to find a signature in $input. Returns the name of the user, false on failure.
204 | * @param $input
205 | * @param $iffy
206 | *
207 | * @return bool|string false if not found Signature, or the Signature if it is found
208 | */
209 | protected function findSigInLine( $input, &$iffy ) {
210 | $iffy = 0;
211 |
212 | $parsee_array = explode( "\n", $input );
213 | for( $n = 0; $n < count( $parsee_array ); $n++ ){ //This for will terminate when a sig is found.
214 | $parsee = $parsee_array[$n];
215 | //Okay, let's try and remove "copied from above" messages. If the line has more than one timestamp, we'll disregard anything after the first.
216 | //Note: we're ignoring people who use custom timestamps - if these peoples' votes are moved, the mover's name will show up as having voted.
217 |
218 | //If more than one timestamp is found in the first portion of the vote:
219 | $tsmatches = array();
220 | $dummymatches = array();
221 | if( preg_match_all( '/' . "[0-2][0-9]\:[0-5][0-9], [1-3]?[0-9] (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{4} \(UTC\)" . '/', $parsee, $tsmatches, PREG_OFFSET_CAPTURE ) > 1 ) {
222 | //Go through each timestamp-section, looking for a signature
223 | foreach( $tsmatches[0] as $minisection ){
224 | $temp = substr( $parsee, 0, $minisection[1] );
225 | //If a signature is found, stop and use it as voter
226 | if( $this->findSig( $temp, $dummymatches ) != 0 ) { //KNOWN ISSUE: Write description later
227 | $parsee = $temp;
228 | break;
229 | }
230 | }
231 | }
232 |
233 | //Start the main signature-finding:
234 | $matches = array();
235 | if( $this->findSig( $parsee, $matches ) == 0 ) {
236 | //Okay, signature not found. Let's try the backup regex
237 | if( $this->findSigAlt( $parsee, $matches ) == 0 ) {
238 | //Signature was not found in this iteration of the main loop :(
239 | continue; //Go on to next newline (may be iffy)
240 | } else {
241 | $merged = array_merge( $matches[1], $matches[2] );
242 | }
243 | } else {
244 | //Merge the match arrays:
245 | $merged = array_merge( $matches[5], $matches[1], $matches[3], $matches[2], $matches[4] );
246 | }
247 | //Remove blank values and arrays of the form ('',-1):
248 | foreach( $merged as $key => $value ){
249 | if( is_array( $value ) && ( $value[0] == '' ) && ( $value[1] == -1 ) ) {
250 | unset( $merged[$key] );
251 | } elseif( $value == "" ) {
252 | unset( $merged[$key] );
253 | }
254 | }
255 |
256 | //Let's find out the real signature
257 | $keys = array();
258 | $values = array();
259 | foreach( $merged as $mergee ){
260 | $keys[] = $mergee[0];
261 | $values[] = $mergee[1];
262 | }
263 | //Now sort:
264 | array_multisort( $values, SORT_DESC, SORT_NUMERIC, $keys );
265 | //Now we should have the most relevant match (i.e., the sig) at the top of $keys
266 | $i = 0;
267 | $foundsig = '';
268 | while( $foundsig == '' ){
269 | $foundsig = trim( $keys[$i++] );
270 | if( $i == count( $keys ) ) break; //If we can only find blank usernames in the sig, catch overflow
271 | //Also fires when the first sig is also the last sig, so not an error
272 | }
273 |
274 | //Set iffy flag (level 1) if went beyond first line
275 | if( $n > 0 ) {
276 | $iffy = 1;
277 | }
278 | return $foundsig;
279 | }
280 |
281 | return false;
282 | }
283 |
284 | /**
285 | * Analyzes an RFA section. Returns an array of parsed signatures on success. Undefined behaviour on failure.
286 | * @param string $input
287 | *
288 | * @return array
289 | */
290 | private function analyzeSection( $input ) {
291 | //Remove trailing sharp, if any
292 | $input = preg_replace( '/#\s*$/', '', $input );
293 |
294 | //Old preg_split regex: "/(^|\n)\s*\#[^\#\:\*]/"
295 | $parsed = preg_split( "/(^|\n)\#/", $input );
296 | //Shift off first empty element:
297 | array_shift( $parsed );
298 |
299 | foreach( $parsed as &$parsee ){ //Foreach line
300 | //If the line is empty for some reason, ignore
301 | $parsee = trim( $parsee );
302 | if( empty( $parsee ) ) continue;
303 |
304 | //If the line has been indented (disabled), or is a comment, ignore
305 | if( ( $parsee[0] == ':' ) || ( $parsee[0] == '*' ) || ( $parsee[0] == '#' ) ) {
306 | $parsee = '///###///';
307 | continue;
308 | }; //struck-out vote or comment
309 |
310 | $parsedsig = $this->findSigInLine( $parsee, $iffy ); //Find signature
311 | $orgsig = $parsee;
312 | $parsee = array();
313 | $parsee['context'] = $orgsig;
314 | if( $parsedsig === false ) {
315 | $parsee['error'] = 'Signature not found';
316 | } else {
317 | $parsee['name'] = $parsedsig;
318 | }
319 | if( @$iffy == 1 ) {
320 | $parsee['iffy'] = '1';
321 | }
322 | } //Foreach line
323 |
324 | if( ( count( $parsed ) == 1 ) && ( @trim( $parsed[0]['name'] ) == '' ) ) { //filters out placeholder sharp sign used in empty sections
325 | $parsed = array();
326 | }
327 |
328 | //Delete struck-out keys "continued" in foreach
329 | foreach( $parsed as $key => $value ){
330 | if( $value == '///###///' ) {
331 | unset( $parsed[$key] );
332 | }
333 | }
334 |
335 | return $parsed;
336 | }
337 |
338 | }
339 |
--------------------------------------------------------------------------------
/Plugins/RPED.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class RPED {
21 |
22 | /**
23 | * Wiki class
24 | *
25 | * @var Wiki
26 | * @access private
27 | */
28 | private $wiki;
29 |
30 | /**
31 | * maxURLLength
32 | * Default maximum length of the URL to be posted
33 | *
34 | * @var int
35 | * @access private
36 | */
37 | private $defaultMaxURLLength;
38 |
39 | /**
40 | * Construction method for the RPED class
41 | *
42 | * @access public
43 | * @param Wiki &$wikiClass The Wiki class object
44 | */
45 | function __construct( Wiki &$wikiClass ) {
46 | $this->wiki = $wikiClass;
47 | $defaultMaxURLLength = 2000;
48 | return;
49 | }
50 |
51 | /**
52 | * Insert a page title into the rped_page table
53 | *
54 | * @static
55 | * @access public
56 | * @param string $page
57 | * @return void
58 | */
59 | public function insert( $page ) {
60 | $this->wiki->apiQuery(
61 | array(
62 | 'action' => 'rped',
63 | 'insert' => $page,
64 | ), true
65 | );
66 | }
67 |
68 | /**
69 | * Delete a page title from the rped_page table
70 | *
71 | * @static
72 | * @access public
73 | * @param string $page
74 | * @return void
75 | */
76 | public function delete( $page ) {
77 | $this->wiki->apiQuery(
78 | array(
79 | 'action' => 'rped',
80 | 'delete' => $page,
81 | ), true
82 | );
83 | }
84 |
85 | /**
86 | * Insert/delete an array of page titles into/from the rped_page table
87 | *
88 | * @static
89 | * @access public
90 | * @param string $command Either 'insert' or 'delete'
91 | * @param array $pageArray The array of page title to insert
92 | * @param int $maxURLLength The maximum length of the url to be POSTed
93 | * @return void
94 | */
95 | public function insertOrDeleteArray( $command, $pageArray, $maxURLLength = 0 ) {
96 | if( $command != 'insert' && $command != 'delete' ) {
97 | die( 'Something tried to call insertOrDeleteArray without'
98 | . 'specifying an insert or delete command.' );
99 | }
100 | if( $maxURLLength == 0 ) {
101 | $maxURLLength = $this->defaultMaxURLLength;
102 | }
103 | $line = '';
104 | foreach( $pageArray as $page ){
105 | if( $line != '' ) {
106 | $line .= '|';
107 | }
108 | if( strlen( $line ) + strlen( $page ) > $maxURLLength ) {
109 | if( $command == 'delete' ) {
110 | $this->delete( $line );
111 | } else {
112 | $this->insert( $line );
113 | }
114 | $line = '';
115 | }
116 | $line .= $page;
117 | }
118 | if( $command == 'delete' ) {
119 | $this->delete( $line );
120 | } else {
121 | $this->insert( $line );
122 | }
123 | }
124 |
125 | /**
126 | * Insert an array of page titles into/from the rped_page table
127 | *
128 | * @static
129 | * @access public
130 | * @param array $pageArray The array of page title to insert
131 | * @param int $maxURLLength The maximum length of the url to be POSTed
132 | * @return void
133 | */
134 | public function insertArray( $pageArray, $maxURLLength = 0 ) {
135 | $this->insertOrDeleteArray( 'insert', $pageArray, $maxURLLength );
136 | }
137 |
138 | /**
139 | * Delete an array of page titles from the rped_page table
140 | *
141 | * @static
142 | * @access public
143 | * @param array $pageArray The array of page title to insert
144 | * @param int $maxURLLength The maximum length of the url to be POSTed
145 | * @return void
146 | */
147 | public function deleteArray( $pageArray, $maxURLLength = 0 ) {
148 | $this->insertOrDeleteArray( 'delete', $pageArray, $maxURLLength );
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Plugins/SiteMatrix.php:
--------------------------------------------------------------------------------
1 | .
18 | */
19 |
20 | class SiteMatrix {
21 |
22 | /**
23 | * Loads list of all SiteMatrix wikis
24 | *
25 | * @static
26 | * @access public
27 | * @param Wiki &$wikiClass The Wiki class object
28 | * @return array List of all wikis
29 | * @throws AssertFailure
30 | * @throws DependencyError
31 | * @throws LoggedOut
32 | * @throws MWAPIError
33 | */
34 | public static function load( Wiki &$wikiClass ) {
35 |
36 | if( !array_key_exists( 'SiteMatrix', $wikiClass->get_extensions() ) ) {
37 | throw new DependencyError( "SiteMatrix" );
38 | }
39 |
40 | $SMres = $wikiClass->apiQuery(
41 | array(
42 | 'action' => 'sitematrix',
43 | )
44 | );
45 |
46 | $wikis = $SMres['sitematrix'];
47 | //return $wikis;
48 |
49 | $retarray = array(
50 | 'raw' => $wikis,
51 | 'urls' => array(),
52 | 'langs' => array(),
53 | 'names' => array(),
54 | 'privates' => array()
55 | );
56 |
57 | foreach( $wikis as $site ){
58 | if( is_array( $site ) ) {
59 | if( isset( $site['site'] ) ) {
60 |
61 | $retarray['langs'][] = $site['code'];
62 | $retarray['names'][$site['code']] = $site['name'];
63 |
64 | foreach( $site['site'] as $site2 ){
65 | $retarray['urls'][] = $site2['url'];
66 |
67 | if( isset( $site2['private'] ) ) $retarray['privates'][] = $site2;
68 | }
69 | } else {
70 | foreach( $site as $site2 ){
71 | $sites2['urls'][] = $site2['url'];
72 |
73 | if( isset( $site2['private'] ) ) $retarray['privates'][] = $site2;
74 | }
75 | }
76 | }
77 | }
78 |
79 | return $retarray;
80 |
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Peachy MediaWiki Framework
2 | =======
3 |
4 | This is a framework for Mediawiki sites
5 |
6 | If you have feature requests please file a bug.
7 |
8 | ###### Badges
9 |
10 | * Status: [](https://travis-ci.org/MW-Peachy/Peachy)
11 | * Coverage: [](https://scrutinizer-ci.com/g/MW-Peachy/Peachy/)
12 | * Quality: [](https://scrutinizer-ci.com/g/MW-Peachy/Peachy/)
13 |
14 | ###### Addwiki (alternative)
15 |
16 | For a more modern set of libraries please consider taking a look at the addwiki PHP libraries.
17 |
18 | * GitHub: https://github.com/addwiki
19 | * Docs: https://addwiki.github.io/
20 | * Packagist: https://packagist.org/packages/addwiki/
21 |
--------------------------------------------------------------------------------
/Tests/GenFunctionsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals( $expected, iin_array( $needle, $haystack ) );
37 | }
38 |
39 | /**
40 | * Data Provider for test_strtoupper_safe()
41 | *
42 | * @return array
43 | */
44 | public function provide_strtoupper_safe() {
45 | return array(
46 | array( 'foo', 'FOO' ),
47 | array( 'FOO', 'FOO' ),
48 | array( array( 'foo' ), array( 'FOO' ) ),
49 | array( array( array( 'foo' ) ), array( array( 'FOO' ) ) ),
50 | array( array( array( 'foo' ), array( 'boo' ), 'F' ), array( array( 'FOO' ), array( 'BOO' ), 'F' ) ),
51 | );
52 | }
53 |
54 | /**
55 | * @dataProvider provide_strtoupper_safe
56 | * @covers ::strtoupper_safe
57 | * @param $input
58 | * @param $expected
59 | */
60 | public function test_strtoupper_safe( $input, $expected ) {
61 | $this->assertSame( $expected, strtoupper_safe( $input ) );
62 | }
63 |
64 | /**
65 | * Data Provider for test_in_string()
66 | *
67 | * @return array
68 | */
69 | public function provide_in_string() {
70 | return array(
71 | array( 'B', 'aBc', false, true ),
72 | array( 'b', 'aBc', false, false ),
73 | array( 'b', 'aBc', true, true ),
74 | );
75 | }
76 |
77 | /**
78 | * @dataProvider provide_in_string
79 | * @covers ::in_string
80 | * @param $needle
81 | * @param $haystack
82 | * @param $insensitive
83 | * @param $expected
84 | */
85 | public function test_in_string( $needle, $haystack, $insensitive, $expected ) {
86 | $this->assertEquals( $expected, in_string( $needle, $haystack, $insensitive ) );
87 | }
88 |
89 | /**
90 | * Data Provider for test_in_array_recursive()
91 | *
92 | * @return array
93 | */
94 | public function provide_in_array_recursive() {
95 | return array(
96 | array( 'BOO', array( 'boo' ), true, true ),
97 | array( 'BOO', array( 'BOO' ), true, true ),
98 | array( 'boo', array( 'BOO' ), true, true ),
99 | array( 'boo', array( 'BOO', 'boo' ), true, true ),
100 | array( 'boo', array(), true, false ),
101 | array( '', array( 'boo' ), true, false ),
102 | array( 'boo', array( array( array( 'boo' ) ) ), false, true ),
103 | array( 'boo', array( array( array( 'foo' => 'boo' ) ) ), false, true ),
104 | array( 'boo', array( array( array( 'foo' => 'BOO' ) ) ), true, true ),
105 | array( 'boo', array( array( array( 'foo' => 'moo' ) ) ), false, false ),
106 | );
107 | }
108 |
109 | /**
110 | * @dataProvider provide_in_array_recursive
111 | * @covers ::in_array_recursive
112 | * @param $needle
113 | * @param $haystack
114 | * @param $insensitive
115 | * @param $expected
116 | */
117 | public function test_in_array_recursive( $needle, $haystack, $insensitive, $expected ) {
118 | $this->assertEquals( $expected, in_array_recursive( $needle, $haystack, $insensitive ) );
119 | }
120 |
121 | /**
122 | * Data Provider for test_checkExclusion()
123 | *
124 | * @return array
125 | */
126 | public function provide_checkExclusion() {
127 | return array(
128 | array( false, '' ),
129 | array( false, '{{bots}}' ),
130 | array( true, '{{nobots}}' ),
131 | array( false, '{{nobots|allow=addbot}}', 'addbot' ),
132 | array( true, '{{nobots|deny=addbot}}', 'addbot' ),
133 | );
134 | }
135 |
136 | /**
137 | * @dataProvider provide_checkExclusion
138 | * @covers ::checkExclusion
139 | * @param $expected
140 | * @param $text
141 | * @param null $pgUsername
142 | * @param null $optout
143 | */
144 | public function test_checkExclusion( $expected, $text, $pgUsername = null, $optout = null ) {
145 | $this->assertSame( $expected, checkExclusion( $this->getMockWiki(), $text, $pgUsername, $optout ) );
146 | }
147 |
148 | /**
149 | * @return \Wiki
150 | */
151 | private function getMockWiki() {
152 | $mock = $this->getMockBuilder( 'Wiki' )
153 | ->disableOriginalConstructor()
154 | ->getMock();
155 | $mock->expects( $this->once() )
156 | ->method( 'get_nobots' )
157 | ->will( $this->returnValue( true ) );
158 | return $mock;
159 | }
160 |
161 | }
--------------------------------------------------------------------------------
/Tests/Includes/AutoUpdateTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder( 'HTTP' )
59 | ->disableOriginalConstructor()
60 | ->getMock();
61 | $mock->expects( $this->any() )
62 | ->method( 'get' )
63 | ->will( $this->returnValue( json_encode( $data ) ) );
64 | return $mock;
65 | }
66 |
67 | /**
68 | * @return array
69 | */
70 | public function provideCheckforupdate()
71 | {
72 | return array(
73 | array(
74 | true,
75 | array( 'message' => 'API rate limit exceeded' ),
76 | '/Cant check for updates right now, next window in/'
77 | ),
78 | array(
79 | false,
80 | array( 'commit' => array( 'sha' => 'testshahash' ) ),
81 | '/Peachy Updated! Changes will go into effect on the next run./',
82 | ),
83 | array(
84 | true,
85 | array( 'commit' => array( 'sha' => 'testshahash' ) ),
86 | '/Peachy is up to date/',
87 | serialize( array( 'commit' => array( 'sha' => 'testshahash' ) ) )
88 | ),
89 | array(
90 | false,
91 | array( 'commit' => array( 'sha' => 'testshahash' ) ),
92 | '/Peachy Updated! Changes will go into effect on the next run./',
93 | serialize( array( 'commit' => array( 'sha' => 'differenthash!' ) ) )
94 | ),
95 | );
96 | }
97 |
98 | /**
99 | * @dataProvider provideCheckforupdate
100 | * @covers AutoUpdate::Checkforupdate
101 | * @covers AutoUpdate::cacheLastGithubETag
102 | * @covers AutoUpdate::getTimeToNextLimitWindow
103 | * @covers AutoUpdate::__construct
104 | * @covers AutoUpdate::updatePeachy
105 | * @covers AutoUpdate::getLocalPath
106 | * @covers AutoUpdate::copyOverGitFiles
107 | * @covers AutoUpdate::rrmdir
108 | * @param $expected
109 | * @param $data
110 | * @param string $outputRegex
111 | * @param null $updatelog
112 | * @param bool $experimental
113 | * @param bool $wasexperimental
114 | */
115 | public function testCheckforupdate( $expected, $data, $outputRegex = '/.*?/', $updatelog = null, $experimental = false, $wasexperimental = false ) {
116 | $updater = $this->getUpdater( $this->getMockHttp( $data ) );
117 | if( $updatelog === null ) {
118 | if( file_exists( __DIR__ . '/../../Includes/' . ( $experimental ? 'Update.log' : 'StableUpdate.log' ) ) ) {
119 | unlink( __DIR__ . '/../../Includes/' . ( $experimental ? 'Update.log' : 'StableUpdate.log' ) );
120 | }
121 | } else {
122 | file_put_contents( __DIR__ . '/../../Includes/' . ( $experimental ? 'Update.log' : 'StableUpdate.log' ), $updatelog );
123 | }
124 |
125 | if( $wasexperimental ) {
126 | file_put_contents( __DIR__ . '/../../Includes/updateversion', serialize( 'master' ) );
127 | } else file_put_contents( __DIR__ . '/../../Includes/updateversion', serialize( 'stable' ) );
128 |
129 | $this->expectOutputRegex( $outputRegex );
130 | $result = $updater->Checkforupdate();
131 | if( !$result ) $updater->updatePeachy();
132 | $this->assertEquals( $expected, $result );
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/Tests/Includes/PageTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString( "Getting page info for page ID {$pageid}..\n\n" );
30 | $expectedPageId = $pageid;
31 | } else {
32 | $this->expectOutputString( "Getting page info for {$title}..\n\n" );
33 | $expectedPageId = 1234;
34 | }
35 |
36 | $page = new Page( $this->getMockWiki(), $title, $pageid, $followRedir, $normalize, $timestamp );
37 |
38 | $this->assertEquals( $expectedPageId, $page->get_id() );
39 | $this->assertEquals( 0, $page->get_namespace() );
40 | $this->assertEquals( 'Foo', $page->get_title() );
41 | $this->assertEquals( 76, $page->get_length() );
42 | $this->assertEquals( false, $page->redirectFollowed() );
43 | $this->assertEquals( 66654, $page->get_talkID() );
44 | $this->assertEquals( '', $page->get_preload() );
45 | }
46 |
47 | /**
48 | * Provides data for testValidConstruction()
49 | *
50 | * @return array
51 | */
52 | public function provideValidConstruction() {
53 | return array(
54 | array( 'Foo' ),
55 | array( 'Foo', 1234 ),
56 | array( 'Foo', 1234, false ),
57 | array( 'Foo', 1234, true ),
58 | array( 'Foo', 1234, false, true ),
59 | array( 'Foo', 1234, true, false ),
60 | array( 'Foo', 1234, true, true, '20141212121212' ),
61 | array( 'Foo', 1234, true, true, 2014 ),
62 | array( 'Foo', 1234, true, true, 20141212121212 ),
63 | );
64 | }
65 |
66 | /**
67 | * Test for invalid construction of the Page class
68 | *
69 | * @covers Page::__construct
70 | * @dataProvider provideInvalidConstruction
71 | *
72 | * @param $expectedException
73 | * @param $wiki
74 | * @param $title
75 | * @param null $pageid
76 | * @param bool $followRedir
77 | * @param bool $normalize
78 | * @param null $timestamp
79 | */
80 | public function testInvalidConstruction( $expectedException, $wiki, $title , $pageid = null , $followRedir = true , $normalize= true, $timestamp = null ) {
81 | $this->setExpectedException( $expectedException );
82 | new Page( $wiki, $title, $pageid, $followRedir, $normalize, $timestamp );
83 | }
84 |
85 | /**
86 | * Data provider for testInvalidConstruction()
87 | *
88 | * @return array
89 | */
90 | public function provideInvalidConstruction() {
91 | return array(
92 | array( 'NoTitle', $this->getMockWiki(), null, null ),
93 | array( 'InvalidArgumentException', $this->getMockWiki(), new stdClass() ),
94 | array( 'InvalidArgumentException', $this->getMockWiki(), 'ImATitle', new stdClass() ),
95 | array( 'InvalidArgumentException', $this->getMockWiki(), 'ImATitle', null, null ),
96 | array( 'InvalidArgumentException', $this->getMockWiki(), 'ImATitle', null, true, null ),
97 | array( 'InvalidArgumentException', $this->getMockWiki(), 'ImATitle', null, true, true, new stdClass() ),
98 | );
99 | }
100 |
101 | /**
102 | * @return \Wiki
103 | */
104 | private function getMockWiki() {
105 | $mock = $this->getMockBuilder( 'Wiki' )
106 | ->disableOriginalConstructor()
107 | ->getMock();
108 | $mock->expects( $this->any() )
109 | ->method( 'get_namespaces' )
110 | ->will( $this->returnValue( array( 0 => '', 1 => 'Talk' ) ) );
111 | $mock->expects( $this->any() )
112 | ->method( 'apiQuery' )
113 | ->will(
114 | $this->returnCallback(
115 | function ( $params ) {
116 | if( $params['action'] === 'query'
117 | && $params['prop'] === 'info'
118 | && $params['inprop'] === 'protection|talkid|watched|watchers|notificationtimestamp|subjectid|url|readable|preload|displaytitle'
119 | ) {
120 | if( array_key_exists( 'titles', $params ) ) {
121 | $title = $params['titles'];
122 | $pageid = 1234;
123 | } else {
124 | $title = 'Foo';
125 | $pageid = $params['pageids'];
126 | }
127 | return array(
128 | 'query' => array(
129 | 'pages' => array(
130 | $pageid => array(
131 | 'pageid' => $pageid,
132 | 'ns' => 0,
133 | 'title' => $title,
134 | 'contentmodel' => 'wikitext',
135 | 'pagelanguage' => 'en',
136 | 'touched' => '2014-01-26T01:13:44Z',
137 | 'lastrevid' => 999,
138 | 'counter' => '',
139 | 'length' => 76,
140 | 'redirect' => '',
141 | 'protection' => array(),
142 | 'notificationtimestamp' => '',
143 | 'talkid' => '66654',
144 | 'fullurl' => 'imafullurl',
145 | 'editurl' => 'imaediturl',
146 | 'readable' => '',
147 | 'preload' => '',
148 | 'displaytitle' => 'Foo',
149 | )
150 | )
151 | )
152 | );
153 | }
154 | return array();
155 | }
156 | )
157 | );
158 | return $mock;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Tests/Includes/PeachyAWBFunctionsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals( $expected, PeachyAWBFunctions::fixDateTags( $text ) );
50 | }
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/Tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Peachy Documentation
4 |
5 | .
6 | */Tests/*,Tests/*
7 |
8 | clean
9 |
10 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 | Tests
19 |
20 |
21 |
22 |
23 | Includes
24 | Plugins
25 | Scripts
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------