Added BetterCodes Repository to README.
[phpwikibot.git] / bot.class.php
blob7236ee346d5862b67988e5c908266ce5ccfc45a6
1 <?php
2 /**
3 * PHPwikiBot Main Class File
4 * @author Xiaomao
5 * @package PHPwikiBot
6 * @name PHPwikiBot main class
7 * @license http://www.gnu.org/licenses/gpl.html GPLv3+
8 */
10 /**
11 * Include some definition and configuration
13 require_once dirname(__FILE__).'/stddef.inc';
14 require_once INC.'cfgparse.php';
15 require_once INC.'exception.inc';
16 require_once INC.'data.class.php';
19 /**
20 * The main class for the bot
22 * @package PHPwikiBot
24 class PHPwikiBot {
25 /**
26 * Current User Config in config.yml
27 * @var array
29 protected $conf;
30 /**
31 * Username of the bot user on the wiki
32 * @var string
34 public $user;
35 /**
36 * Absolute path to API
37 * @var string
39 public $api_url;
40 /**
41 * Bot's user-agent
42 * @var string
44 public $useragent;
45 /**
46 * A key in $wiki array
47 * @var string
49 protected $wikid;
50 /**
51 * Wiki's full name or a friendly name
52 * @var string
54 public $wikiname;
55 /**
56 * Replicate DB's slave some times have trouble syncing, set this to 5
57 * @var int
59 public $max_lag = 5; // fix slave db's lag problem
60 /**
61 * Whether to output some unimportant messages
62 * @var bool
64 protected $out = true;
65 /**
66 * Frequency of edit
67 * @var float
69 public $epm;
70 /**
71 * cURL POST handle
72 * @var resource
74 protected $post;
75 /**
76 * cURL GET handle
77 * @var resource
79 protected $get;
80 /**
81 * Log level
82 * @var int
84 protected $loglevel = LOG_INFO;
85 /**
86 * fopen() handle of the log file
87 * @var resource
89 protected $logh;
90 /**
91 * friendly name of log level
92 * @var array
94 protected $loglevelname = array(
95 LG_INFO => 'Info',
96 LG_DEBUG => 'Debug Info',
97 LG_NOTICE => 'Notice',
98 LG_WARN => 'Warning',
99 LG_ERROR => 'Error',
100 LG_FATAL => 'Fatal Error',
103 * Whether to output the log to STDOUT/STDERR
104 * @var array
106 protected $output_log = true;
108 * Data to pass to put_page() from create_page() or edit_page()
109 * @var array
111 protected $editdetails;
113 /* Magic Functions */
115 * Constructor, initialize the object and login
117 * @param string $user A username in config.php
118 * @param bool $slient Be quiet
119 * @return void This function throws exception rather than return value since constructor doesn't return
120 * @throws LoginFailure from PHPwikiBot::login
123 public function __construct($user, $slient = false) {
124 /** Configuration Mapping */
125 $this->conf = $GLOBALS['users'][$user]; // Map the user configuration array
126 $this->useragent = $GLOBALS['useragent']; // Define the user-agent
127 $this->wikid = $this->conf['wiki']; // Wiki ID
128 $this->wikiname = $GLOBALS['wiki'][$this->wikid]['name'];// Wiki's name
129 $this->api_url = $GLOBALS['wiki'][$this->wikid]['api'];// Path to API
130 $this->epm = 60 / $GLOBALS['wiki'][$this->wikid]['epm']; // Edit per minute
131 $this->user = $this->conf['name']; // Username
133 * If the server supports OpenSSL, use encrypted password or else use plain text
135 if (function_exists('openssl_decrypt'))
136 $pass = openssl_decrypt($this->conf['password'], 'AES-128-ECB', $GLOBALS['key']); // Password
137 else
138 $pass = $this->conf['password']; // Password
139 if ($slient) $this->out = false; // Not yet used
140 /** Log */
141 $this->loglevel = $GLOBALS['log_level']; // Loglevel
142 $this->logh = fopen($GLOBALS['logfile'], 'a');// Open Logfile
143 $this->output_log = $GLOBALS['output_log']; // Whether to output logs to stdout/stderr
144 /** Initialize */
145 $this->conninit(); // Initialize cURL handles
146 // Trying to Login
147 try {
148 $this->login($user, $pass);
149 } catch (LoginFailure $e) {
150 throw $e;
155 * Clear the cookies when the script terminates or the bot object is destroyed
157 function __destruct() {
158 $this->logout(); // Logs out
159 fclose($this->logh); // Close the log handle
163 * Convert the object to string
165 * @return string Basic info about this object
168 function __toString() {
169 // Get all required info
170 $name = $this->user; // User name
171 $wikid = $this->wikid; // Wiki ID in configuration
172 $wikiname = $this->wikiname; // Wiki Name
173 $useragent = $this->useragent; // Bot User-Agent
174 $api = $this->api_url; // Path to API
175 if (function_exists('openssl_decrypt')) $crypt = 'yes'; // Encrypted password
176 else $crypt = 'no';
177 return <<<EOD
178 Username: $name
179 Encrypted Password: $crypt
180 Wiki ID: $wikid
181 Wiki Name: $wikiname
182 User Agent: $useragent
184 EOD;
189 * Unexisted Method are sometimes called, in this case, a 10 Unexist Method is thrown
191 * @param string $name The name of the function
192 * @param array $arguments Array of arguments
193 * @return void No return value
194 * @throws BotException
196 public function __call($name, $arguments) {
197 // Logs before throwing exceptions
198 $this->log('Called unexist method "'.$name.'('.implode(', ', $arguments).'"!', LG_FATAL);
199 throw new BotException('Unexist Method', 10); // Called to Unexisted Method
202 /* Public Callable Methods */
204 * Get General wiki info
206 * @param string $type The name of the setting, leave blank for all
207 * @return mixed Either the value of $type or an array contain all info
208 * @tutorial ./tutorial/gnlwikinfo.txt
211 public function wiki_info ($type = '') {
212 $response = $this->getAPI('action=query&meta=siteinfo'); // Query the server
213 if (!is_array($response)) throw new InfoFailure ('Can\'t Get Info', 300); // Failure
214 if ($type):
215 if (isset($response['query']['general'][$type]))
216 return $response['query']['general'][$type]; // Returns the information in $type
217 else
218 throw new InfoFailure ('Not in Gereral Info', 301); // Doesn't have it
219 else:
220 return $response['query']['general']; // Return all
221 endif;
225 * Fetch a page from the wiki
227 * @param string $page The page name
228 * @return object A instance of WikiPage containing all information this function can get
229 * @throws GetPageFailure when failure
232 public function get_page($page, $internal = false) {
233 $response = $this->getAPI('action=query&prop=revisions&titles='.urlencode($page).'&rvprop=content'); // Query
234 //var_dump($response);
235 if (is_array($response)) { // Make sure there are no errors
236 $array = $response['query']['pages']; // Create a temperory variable
237 //var_dump($array);
238 foreach ($array as $v) {
239 if (isset($v['missing'])):
240 if (!$internal) // Only logs in out-class use
241 $this->log('Page \''.$page.'\' doesn\'t exist!', LG_ERROR);
242 throw new GetPageFailure('Page doesn\'t exist', 201);
243 elseif (isset($v['invalid'])):
244 if (!$internal) // Only logs in out-class use
245 $this->log('Page title \''.$page.'\' is invalid!', LG_ERROR);
246 throw new GetPageFailure('Page title invaild', 202);
247 elseif (isset($v['special'])):
248 if (!$internal) // Only logs in out-class use
249 $this->log('Page \''.$page.'\' is a special page!', LG_ERROR);
250 throw new GetPageFailure('Special Page', 203);
251 else:
252 if (isset($v['revisions'][0]['*']) && is_string($v['revisions'][0]['*'])): // If every thing is proper
253 $i = new WikiPage; // Create a new WikiPage class
254 $i->text = $v['revisions'][0]['*']; // Page text
255 $i->title = $v['title']; // Title
256 $i->ns = $v['ns']; // Namespace ID
257 $i->id = $v['pageid']; // Page ID
258 $j = strstr($i->title, ':', true); // Namespace Name
259 if ($j)
260 $i->nsname = $j; // A page with named namespace
261 else
262 $i->nsname = true; // The main namespace
263 return $i; // Returns the class
264 else:
265 $this->log('Can\' fetch page \''.$page.'\' for some reason!', LG_ERROR);
266 throw new GetPageFailure('Can\'t Fetch Page', 200);
267 endif;
268 endif;
270 } else {
271 $this->log('Can\' fetch page \''.$page.'\' for some reason!', LG_ERROR);
272 throw new GetPageFailure('Can\'t Fetch Page', 200);
277 * Get a page's category
279 * @param string $page The page name
280 * @return array An array with all categories or false if no category
283 public function get_page_cat($page) {
284 $response = $this->postAPI('action=query&prop=categories&titles='.urlencode($page)); // Query
285 //var_dump($response);
286 foreach ($response['query']['pages'] as $key => $value) { // Foreach into the array
287 //var_dump($value);
288 if (!isset($value['categories'])) return false; // No categories
289 foreach ($value['categories'] as $key2 => $value2)
290 $cats[] = $value2['title']; // Get all categories into the array
292 //var_dump($cats);
293 return $cats;
298 * Get all page in a category
300 * @param string $category Name of Category
301 * @param int $limit Number of page to fetch before it stops
302 * @param string $start Start from page name
303 * @param string $ns Namespace, 'all' for all
304 * @return array Array of page
307 public function category($category, $limit = 500, $start = '', $ns = 'all') {
308 $query = 'action=query&list=categorymembers&cmtitle=' . urlencode('Category:' . $category) . '&cmlimit=' . $limit; // Query template
309 if ($ns != 'all')
310 $query .= '&cmnamespace=' . $ns; // Namespace Selection
311 if ($start != '')
312 $query .= '&cmcontinue=' . urlencode($start); // Begins at page..., Continuing
313 $result = $this->postAPI($query); // Query
314 $cm = $result['query']['categorymembers']; // Temp variable
315 $pages = array(); // Array of pages
316 $j = count($cm); // Amount of pages
317 for ($i = 0; $i < $j; ++$i)
318 $pages[] = $cm[$i]['title']; // Get the page name into $pages
319 if (isset($result['query-continue']['categorymembers']['cmcontinue'])) { // Continuing
320 $next = $result['query-continue']['categorymembers']['cmcontinue'];
321 if ($next != '')
322 array_push($pages, $next); // Push the array
324 return $pages; // Returns $pages
328 * Creates a page
330 * @param string $page Page title
331 * @param string $text New Text
332 * @param string $summary Edit Summary
333 * @param bool $minor Minor Edit
334 * @param bool $force Force Edit
335 * @return bool Return true on success
336 * @throws EditFailure
338 public function create_page($page, $text, $summary, $minor = false, $force = false) {
339 $response = $this->postAPI('action=query&prop=info|revisions&intoken=edit&titles=' . urlencode($page)); // Get a token and basic info
340 $this->editdetails = $response['query']['pages']; // Assign info to be used with put_page
341 if (!isset($this->editdetails[-1])) throw new EditFailure('Page Exists', 420); // Page existed
342 $bot = false; // Not using bot account
343 if (isset($this->conf['bot']) && $this->conf['bot'] == true) $bot = true; // Using bot account
344 try { // Try to put_page()
345 $this->put_page($page, $text, $summary, $minor, $bot);
346 return true;
347 } catch (EditFailure $e) {
348 throw $e; // Rethrow the exception
350 $this->editdetails = null; // Clears the shared property
354 * Modifies a page
356 * @param string $page Page title
357 * @param string $text New Text
358 * @param string $summary Edit Summary
359 * @param bool $minor Minor Edit
360 * @param bool $force Force Edit
361 * @return bool Return true on success
362 * @throws EditFailure
364 public function edit_page($page, $text, $summary, $minor = false, $force = false) {
365 $response = $this->postAPI('action=query&prop=info|revisions&intoken=edit&titles=' . urlencode($page)); // Get data
366 $this->editdetails = $response['query']['pages']; // Push the data into shared property
367 if (isset($this->editdetails[-1])) throw new EditFailure('Page Doesn\'t Exist', 421); // Page doesn't exist
368 $bot = false; // Not using bot account
369 if (isset($this->conf['bot']) && $this->conf['bot'] == true) $bot = true; // Using bot account
370 try { // Try to put_page()
371 $this->put_page($page, $text, $summary, $minor, $bot);
372 return true;
373 } catch (EditFailure $e) {
374 throw $e; // Rethrow the exception
376 $this->editdetails = null; // Clears the shared property
380 * Move a page
382 * @param string $from The source page
383 * @param string $to The destination
384 * @param string $reason Reason for moving
385 * @param bool $talk Move talk page
386 * @param bool $sub Move subpages
387 * @param bool $redirect Create a redirect form $from to $to
388 * @return bool Return ture on sucess
389 * @throws MoveFailure
391 public function move_page($from, $to, $reason = '', $talk = true, $sub = true, $redirect = true) {
392 $response = $this->postAPI('action=query&prop=info&intoken=move&titles=' . urlencode($from));
393 //var_dump($response);
394 foreach ($response['query']['pages'] as $v) {
395 if (isset($v['invalid'])) throw new ProtectFailure('Invalid Title', 507);
396 $token = $v['movetoken'];
398 $query = 'action=move&from='.urlencode($from).'&to='.urlencode($to).'&token='.urlencode($token).'&reason='.urlencode($reason);
399 if (!$redirect)
400 $query .= '&noredirect';
401 if ($talk)
402 $query .= '&movetalk';
403 if ($sub)
404 $query .= '&movesubpages';
405 $response = $this->postAPI($query);
406 //var_dump($response);
407 if (isset($response['error'])) {
408 switch ($response['error']['code']):
409 case 'articleexists': // 501 Destination Exists
410 throw new MoveFailure('Destination Exists', 501);
411 break;
412 case 'protectedpage':
413 case 'protectedtitle':
414 case 'immobilenamespace': // 502 Protected
415 throw new MoveFailure('Protected', 502);
416 break;
417 case 'cantmove':
418 case 'cantmovefile':
419 case 'cantmove-anon': // 503 Forbidden
420 throw new MoveFailure('Forbidden', 503);
421 break;
422 case 'filetypemismatch': // 504 Extension Mismatch
423 throw new MoveFailure('Extension Mismatch', 504);
424 break;
425 case 'nonfilenamespace': // 504 Wrong Namespace
426 throw new MoveFailure('Wrong Namespace', 505);
427 break;
428 case 'selfmove': // 506 Self Move
429 throw new MoveFailure('Self Move', 506);
430 break;
431 default:
432 throw new MoveFailure('Move Failure', 500);
433 endswitch;
435 return true;
439 * Deletes a page
441 * @param string $page Page to delete
442 * @param string $reason Reason of deleting
443 * @return bool True when success
444 * @throws DeleteFailure
447 public function del_page($page, $reason = '') {
448 $response = $this->postAPI('action=query&prop=info&intoken=delete&titles=' . urlencode($page));
449 //var_dump($response);
450 if (isset($response['warnings']['info']['*']) && strstr($response['warnings']['info']['*'], 'not allowed'))
451 throw new DeleteFailure('Forbidden', 603);
452 foreach ($response['query']['pages'] as $v) {
453 if (isset($v['invalid'])) throw new ProtectFailure('Invalid Title', 604);
454 $token = $v['deletetoken'];
456 $query = 'action=delete&title='.urlencode($page).'&token='.urlencode($token).'&reason='.urlencode($reason);
457 $response = $this->postAPI($query);
458 if (isset($response['error'])) {
459 switch ($response['error']['code']):
460 case 'cantdelete':
461 case 'missingtitle':
462 $this->log('Failed to delete '.$page.' with error 601 No Such Page', LG_ERROR);
463 throw new DeleteFailure('No Such Page', 601);
464 break;
465 case 'blocked':
466 case 'autoblocked': // 402 Blocked
467 $this->log('Failed to delete '.$page.' with error 602 Blocked', LG_ERROR);
468 throw new DeleteFailure('Blocked', 602);
469 break;
470 case 'permissiondenied':
471 case 'protectedtitle':
472 case 'protectedpage':
473 case 'protectednamespace': // 603 Forbidden
474 $this->log('Failed to delete '.$page.' with error 603 Forbidden', LG_ERROR);
475 throw new DeleteFailure('Forbidden', 603);
476 break;
477 default:
478 $this->log('Failed to delete '.$page.' with error 600 Delete Failure', LG_ERROR);
479 throw new DeleteFailure('Delete Failure', 600);
480 endswitch;
482 return true;
486 * Undeletes a page with all revisions
488 * @param string $page Page name to undelete
489 * @param string $reason Reason of undeleting
490 * @return bool Return true on success
491 * @throws UndeleteFailure
493 public function undel_page($page, $reason = '') {
494 $response = $this->postAPI('action=query&prop=info&intoken=edit&titles=xxxxxxxx');
495 //var_dump($response);
496 foreach ($response['query']['pages'] as $v)
497 $token = $v['edittoken'];
498 //var_dump($token);
499 $query = 'action=undelete&title='.urlencode($page).'&token='.urlencode($token).'&reason='.urlencode($reason);
500 $response = $this->postAPI($query);
501 //var_dump($response);
502 if (isset($response['error'])) {
503 switch ($response['error']['code']) {
504 case 'cantdelete':
505 $this->log('Failed to undelete '.$page.' with error 901 Not Deleted', LG_ERROR);
506 throw new UndeleteFailure('No Such Page', 901);
507 break;
508 case 'blocked':
509 case 'autoblocked': // 402 Blocked
510 $this->log('Failed to undelete '.$page.' with error 902 Blocked', LG_ERROR);
511 throw new UndeleteFailure('Blocked', 902);
512 break;
513 case 'permissiondenied':
514 case 'protectedtitle':
515 case 'protectedpage':
516 case 'protectednamespace': // 603 Forbidden
517 $this->log('Failed to undelete '.$page.' with error 903 Forbidden', LG_ERROR);
518 throw new UndeleteFailure('Forbidden', 903);
519 break;
520 case 'invalidtitle':
521 $this->log('Failed to undelete '.$page.' with error 904 Invaild Title', LG_ERROR);
522 throw new UndeleteFailure('Invaild Title', 904);
523 break;
524 default:
525 $this->log('Failed to undelete '.$page.' with error 900 Delete Failure', LG_ERROR);
526 throw new UneleteFailure('Delete Failure', 900);
529 return true;
533 * Blocks a user
535 * @param string $name Username
536 * @param string $reason Reason for blocking
537 * @param string $exp A realtive(e.g. 2 days) or absolute(yyyymmddhhmmss)
538 * @param bool $nocreate Block the IP from creating acounts
539 * @param bool $auto Block the user's registration IP and any other IP the user tries to logon
540 * @param bool $noemail Blocks the user's ability to send emails
541 * @return bool True on success
542 * @throws BlockFailure
544 public function block ($name, $reason = '', $exp = 'never', $nocreate = false, $auto = false, $noemail = true) {
545 $resp = $this->postAPI('action=query&prop=info&intoken=block&titles=User:'.$name);
546 //var_dump($resp);
547 if (isset($resp['warnings']['info']['*']) && strstr($resp['warnings']['info']['*'], 'not allowed')) {
548 $this->log('Failed to block user '.$name.' with error 1003 Forbidden', LG_ERROR);
549 throw new BlockFailure('Forbidden', 1003);
551 foreach ($resp['query']['pages'] as $v)
552 $token = $v['blocktoken'];
553 //echo $token;
554 $query = 'action=block&user='.urlencode($name).'&expiry='.urlencode($exp).'&token='.urlencode($token);
555 if ($reason)
556 $query .= '&reason='.$reason;
557 else
558 $query .= '&reason='.urlencode('I Hate '.$name);
559 if ($auto)
560 $query .= '&autoblock';
561 if ($nocreate)
562 $query .= '&nocreate';
563 if ($noemail)
564 $query .= '&noemail';
565 $resp = $this->postAPI($query);
566 //var_dump($resp);
567 if (isset($response['error'])) {
568 switch ($response['error']['code']) {
569 case 'alreadyblocked':
570 $this->log('Failed to block user '.$name.' with error 1001 Already Blocked', LG_ERROR);
571 throw new BlockFailure('1001 Already Blocked', 1001);
572 break;
573 case 'blocked':
574 case 'autoblocked':
575 $this->log('Failed to block user '.$name.' with error 1002 Blocked', LG_ERROR);
576 throw new BlockFailure('Blocked', 1002);
577 break;
578 case 'permissiondenied':
579 case 'cantblock':
580 case 'cantblock-email':
581 case 'rangedisabled':
582 $this->log('Failed to block user '.$name.' with error 1003 Forbidden', LG_ERROR);
583 throw new BlockFailure('Forbidden', 1003);
584 break;
585 case 'invalidexpiry':
586 case 'pastexpiry':
587 case 'invalidrange':
588 $this->log('Failed to block user '.$name.' with error 1004 Invaild Expiry', LG_ERROR);
589 throw new BlockFailure('Invaild Expiry', 1004);
590 break;
591 case 'invaliduser':
592 case 'invalidip':
593 $this->log('Failed to block user '.$name.' with error 1005 Invaild User/IP', LG_ERROR);
594 throw new BlockFailure('Invaild User/IP', 1005);
595 break;
596 default:
597 $this->log('Failed to block user '.$name.' with error 1000 Block Failure', LG_ERROR);
598 throw new BlockFailure('Block Failure', 1000);
601 return true;
605 * Unlocks a user
607 * @param string $name Username
608 * @param string $reason Reason for unblocking
609 * @return bool True on success
610 * @throws BlockFailure
612 public function unblock ($name, $reason = '') {
613 $resp = $this->postAPI('action=query&prop=info&intoken=unblock&titles=User:'.$name);
614 //var_dump($resp);
615 if (isset($resp['warnings']['info']['*']) && strstr($resp['warnings']['info']['*'], 'not allowed')) {
616 $this->log('Failed to unblock user '.$name.' with error 1003 Forbidden', LG_ERROR);
617 throw new BlockFailure('Forbidden', 1003);
619 foreach ($resp['query']['pages'] as $v)
620 $token = $v['blocktoken'];
621 //echo $token;
622 $query = 'action=unblock&user='.urlencode($name).'&token='.urlencode($token);
623 if ($reason)
624 $query .= '&reason='.$reason;
625 else
626 $query .= '&reason='.urlencode('Sorry '.$name);
627 $resp = $this->postAPI($query);
628 //var_dump($resp);
629 if (isset($response['error'])) {
630 switch ($response['error']['code']) {
631 case 'blocked':
632 case 'autoblocked':
633 $this->log('Failed to unblock user '.$name.' with error 1002 Blocked', LG_ERROR);
634 throw new BlockFailure('Blocked', 1002);
635 break;
636 case 'permissiondenied':
637 case 'cantunblock':
638 $this->log('Failed to unblock user '.$name.' with error 1003 Forbidden', LG_ERROR);
639 throw new BlockFailure('Forbidden', 1003);
640 break;
641 case 'cantunblock':
642 $this->log('Failed to unblock user '.$name.' with error 1007 Not Blocked', LG_ERROR);
643 throw new BlockFailure('Not Blocked', 1007);
644 break;
645 default:
646 $this->log('Failed to unblock user '.$name.' with error 1000 Unblock Failure', LG_ERROR);
647 throw new BlockFailure('Unblock Failure', 1000);
650 return true;
654 * Protects a page
656 * @param string $page Page title to protect
657 * @param string $edit all=everyone autoconfirmed=Autoconfirmed Users sysop=Administrators
658 * @param string $move all=everyone autoconfirmed=Autoconfirmed Users sysop=Administrators
659 * @param string $reason Reason of protection
660 * @param string $editexp Edit protecting expiry in format yyyymmddhhmmss
661 * @param string $movexp Move protecting expiry in format yyyymmddhhmmss
662 * @param bool $cascade Whether to enable cascade protection, i.e. protect all transcluded tamplates
663 * @return bool Return true on success
664 * @throws ProtectFailure
666 public function protect_page($page, $edit, $move, $reason = '', $editexp = 'never', $movexp = 'never', $cascade = false) {
667 $response = $this->postAPI('action=query&prop=info&intoken=protect&titles=' . urlencode($page));
668 //var_dump($response);
669 if (isset($response['warnings']['info']['*']) && strstr($response['warnings']['info']['*'], 'not allowed'))
670 throw new ProtectFailure('Forbidden', 703);
671 foreach ($response['query']['pages'] as $v) {
672 if (isset($v['invalid'])) throw new ProtectFailure('Invalid Title', 704);
673 $token = $v['protecttoken'];
675 $query = 'action=protect&title='.urlencode($page).'&token='.urlencode($token);
676 if ($reason)
677 $query .= '&reason='.urlencode($reason);
678 $query .= '&protections=edit='.$edit.'|move='.$move;
679 $query .= '&expiry='.$editexp.'|'.$movexp;
680 if ($cascade) $query .= '&cascade';
681 $response = $this->postAPI($query);
682 //var_dump($response);
683 if (isset($response['error'])) {
684 switch ($response['error']['code']) {
685 case 'missingtitle-createonly':
686 $this->log('Failed to protect '.$page.' with error 701 No Such Page', LG_ERROR);
687 throw new ProtectFailure('No Such Page', 701);
688 break;
689 case 'blocked':
690 case 'autoblocked': // 702 Blocked
691 $this->log('Failed to protect '.$page.' with error 702 Blocked', LG_ERROR);
692 throw new ProtectFailure('Blocked', 702);
693 break;
694 case 'cantedit':
695 case 'permissiondenied':
696 case 'protectednamespace': // 703 Forbidden
697 $this->log('Failed to protect '.$page.' with error 703 Forbidden', LG_ERROR);
698 throw new ProtectFailure('Forbidden', 703);
699 break;
700 case 'invalidexpiry': // 705 Invaild Expiry
701 $this->log('Failed to protect '.$page.' with error 705 Invaild Expiry', LG_ERROR);
702 throw new ProtectFailure('Invaild Expiry', 705);
703 break;
704 case 'pastexpiry': // 706 Past Expiry
705 $this->log('Failed to protect '.$page.' with error 706 Past Expiry', LG_ERROR);
706 throw new ProtectFailure('Past Expiry', 706);
707 break;
708 case 'protect-invalidlevel': // 707 Invaild Level
709 $this->log('Failed to protect '.$page.' with error 707 Invaild Level', LG_ERROR);
710 throw new ProtectFailure('Invaild Level', 707);
711 break;
712 default:
713 $this->log('Failed to protect '.$page.' with error 700 Protect Failure', LG_ERROR);
714 throw new ProtectFailure('Protect Failure', 700);
717 return true;
721 * Protects a non-exist page
723 * @param string $page Page title to protect
724 * @param string $perm Permission:all=everyone autoconfirmed=Autoconfirmed Users sysop=Administrators
725 * @param string $reason Reason of protection
726 * @param string $exp Protecting expiry in format yyyymmddhhmmss
727 * @return bool Return true on success
728 * @throws ProtectFailure
730 public function protect_title($page, $perm, $reason = '', $exp = 'never') {
731 $response = $this->postAPI('action=query&prop=info&intoken=protect&titles=' . urlencode($page));
732 //var_dump($response);
733 if (isset($response['warnings']['info']['*']) && strstr($response['warnings']['info']['*'], 'not allowed'))
734 throw new ProtectFailure('Forbidden', 703);
735 foreach ($response['query']['pages'] as $v) {
736 if (isset($v['invalid'])) throw new ProtectFailure('Invalid Title', 704);
737 $token = $v['protecttoken'];
739 $query = 'action=protect&title='.urlencode($page).'&token='.urlencode($token);
740 if ($reason)
741 $query .= '&reason='.urlencode($reason);
742 $query .= '&protections=create='.$perm;
743 $query .= '&expiry='.$exp;
744 $response = $this->postAPI($query);
745 //var_dump($response);
746 if (isset($response['error'])) {
747 switch ($response['error']['code']) {
748 case 'create-titleexists':
749 $this->log('Failed to protect '.$page.' with error 708 Page Exists', LG_ERROR);
750 throw new ProtectFailure('Page Exists', 708);
751 break;
752 case 'blocked':
753 case 'autoblocked': // 702 Blocked
754 $this->log('Failed to protect '.$page.' with error 702 Blocked', LG_ERROR);
755 throw new ProtectFailure('Blocked', 702);
756 break;
757 case 'cantedit':
758 case 'permissiondenied':
759 case 'protectednamespace': // 703 Forbidden
760 $this->log('Failed to protect '.$page.' with error 703 Forbidden', LG_ERROR);
761 throw new ProtectFailure('Forbidden', 703);
762 break;
763 case 'invalidexpiry': // 705 Invaild Expiry
764 $this->log('Failed to protect '.$page.' with error 705 Invaild Expiry', LG_ERROR);
765 throw new ProtectFailure('Invaild Expiry', 705);
766 break;
767 case 'pastexpiry': // 706 Past Expiry
768 $this->log('Failed to protect '.$page.' with error 706 Past Expiry', LG_ERROR);
769 throw new ProtectFailure('Past Expiry', 706);
770 break;
771 case 'protect-invalidlevel': // 707 Invaild Level
772 $this->log('Failed to protect '.$page.' with error 707 Invaild Level', LG_ERROR);
773 throw new ProtectFailure('Invaild Level', 707);
774 break;
775 default:
776 $this->log('Failed to protect '.$page.' with error 700 Protect Failure', LG_ERROR);
777 throw new ProtectFailure('Protect Failure', 700);
780 return true;
784 * Upload a local file or a remote file using URL
786 * @param string $src Source, may be a local file or an URI with a warpper
787 * @param string $target Target file name
788 * @param string $comment Upload comment
789 * @param string $text File page content
790 * @return class a class with file data(UploadData)
791 * @throws UploadFailure
793 function upload($src, $target, $comment = '', $text = '') {
794 $response = $this->postAPI('action=query&prop=info&intoken=edit&titles=xxxxxxxx');
795 //var_dump($response);
796 foreach ($response['query']['pages'] as $v)
797 $token = $v['edittoken'];
798 //echo $token.EOL;
799 if ($this->is_url($src)) {
800 $i = get_headers($src);
801 //var_dump($i);
802 if ($i[0]{9} != 2 and $i[0]{9} != 3) throw new UploadFailure('Can\'t Fetch File', 801);
803 $query = 'action=upload&url='.urlencode($src).'&token='.urlencode($token).'&filename='.urlencode($target);
804 if ($comment) $query .= '&comment='.urlencode($comment);
805 if ($text) $query .= '&text='.urlencode($text);
806 $response = $this->postAPI($query);
807 //var_dump($response);
808 } else {
809 if (!is_readable($src)) throw new UploadFailure('Can\'t Read File', 802);
810 $query = array(
811 'action' => 'upload',
812 'file' => "@$src",
813 'token' => $token,
814 'filename' => $target,
815 'format' => 'php'
817 //var_dump($query['file']);
818 if ($comment) $query['comment'] = $comment;
819 if ($text) $query['text'] = $text;
820 $ch = curl_init();
821 $cfg = array(
822 CURLOPT_RETURNTRANSFER => true,
823 CURLOPT_COOKIEJAR => 'cookie.txt',
824 CURLOPT_COOKIEFILE => 'cookie.txt',
825 CURLOPT_USERAGENT => $this->useragent,
826 CURLOPT_HEADER => false,
827 CURLOPT_URL => $this->api_url,
828 CURLOPT_POST => true,
829 CURLOPT_POSTFIELDS => $query,
830 CURLOPT_HTTPHEADER => array('Content-Type: multipart/form-data'),
832 curl_setopt_array($ch, $cfg);
833 $response = curl_exec($ch);
834 if (curl_errno($ch)) var_dump(curl_error($ch));
835 curl_close($ch);
836 $response = unserialize($response);
838 if (isset($response['error'])) {
839 switch ($response['error']['code']) {
840 case 'empty-file':
841 $this->log('Failed to upload '.$src.' with error 801 Can\'t Fetch File', LG_ERROR);
842 throw new UploadFailure('Can\'t Fetch File', 801);
843 break;
844 case 'permissiondenied':
845 $this->log('Failed to upload '.$src.' with error 803 Forbidden', LG_ERROR);
846 throw new UploadFailure('Forbidden', 803);
847 case 'blocked':
848 case 'autoblocked': // 804 Blocked
849 $this->log('Failed to upload '.$src.' with error 804 Blocked', LG_ERROR);
850 throw new UploadFailure('Blocked', 804);
851 break;
852 default:
853 $this->log('Failed to upload '.$src.' with error 800 Upload Failure', LG_ERROR);
854 throw new UploadFailure('Upload Failure', 800);
857 if ($response['upload']['result'] == 'Success'):
858 $j = $response['upload']['imageinfo'];
859 $this->log('Uploaded '.$src.' to '.$j['descriptionurl'], LG_INFO);
860 $i = new UploadData;
861 $i->timestamp = $j['timestamp'];
862 $i->width = $j['width'];
863 $i->height = $j['height'];
864 $i->url = $j['url'];
865 $i->page = $j['descriptionurl'];
866 $i->mime = $j['mime'];
867 $i->sha1 = $j['sha1'];
868 return $i;
869 endif;
870 throw new UploadFailure('Upload Failure', 800);
874 * Emails A user
876 * @param string $user The User name
877 * @param string $subject Email Subject
878 * @param string $text Content of email
879 * @param bool $cc Send a copy the the sender
880 * @return bool True on success
881 * @throws EmailFailure
883 public function email ($user, $subject, $text, $cc) {
884 $response = $this->postAPI('action=query&prop=info&intoken=email&titles=User%3A' . urlencode($user));
885 //var_dump($response);
886 if (isset($response['warnings']['info']['*']) && strstr($response['warnings']['info']['*'], 'not allowed'))
887 throw new ProtectFailure('Forbidden', 703);
888 foreach ($response['query']['pages'] as $v) {
889 if (isset($v['invalid'])) throw new ProtectFailure('Invalid Title', 704);
890 $token = $v['emailtoken'];
892 //echo $token;
893 $query = 'action=emailuser&target='.urlencode($user).'&subject='.urlencode($subject).'&text='.urlencode($text).'&token='.urlencode($token);
894 if ($cc) $query .= '&ccme';
895 $resp = $this->postAPI($query);
896 //var_dump($resp);
897 if (isset($resp['error'])) switch ($resp['error']['code']) {
898 case 'noemail':
899 case 'usermaildisabled':
900 $this->log('Failed to email '.$user.' with error 1101 Don\'t want email', LG_ERROR);
901 throw new EmailFailure('Don\'t want email', 1101);
902 break;
903 case 'permissiondenied':
904 $this->log('Failed to email '.$user.' with error 1103 Forbidden', LG_ERROR);
905 throw new EmailFailure('Forbidden', 1103);
906 case 'blocked':
907 case 'autoblocked':
908 case 'blockedfrommail':
909 $this->log('Failed to email '.$user.' with error 1102 Blocked', LG_ERROR);
910 throw new EmailFailure('Blocked', 1102);
911 break;
912 default:
913 $this->log('Failed to email '.$user.' with error 1100 Email Failure', LG_ERROR);
914 throw new EmailFailure('Email Failure', 1100);
916 if ($resp['emailuser']['result'] == 'Success') return true;
917 $this->log('Failed to email '.$user.' with error 1100 Email Failure', LG_ERROR);
918 throw new EmailFailure('Email Failure', 1100);
921 public function ns_get() {}
924 * Imports a page from InterWiki
926 * @param string $iw InterWiki name
927 * @param string $src Source page
928 * @param int $ns Target namespace id
929 * @param bool $full Full revision import
930 * @return int Number of revisions imported
931 * @throws ImportFailure
933 public function iwimp($iw, $src, $ns = '', $full = false) {
934 $query = 'action=import&interwikisource='.urlencode($iw).'&interwikipage='.urlencode($src).'&token='.$this->imp_token();
935 if ($ns !== '') $query .= '&namespace='.$ns;
936 if ($full) $query .= '&fullhistory';
937 $resp = $this->postAPI($query);
938 if (isset($resp['error'])) switch ($resp['error']['code']) {
939 case 'unknown_interwikisource':
940 case 'badinterwiki':
941 $this->log('Failed to import page '.$iw.':'.$src.' with error 1201 Wrong Interwiki, Interwiki importing for that InterWiki might be disabled', LG_ERROR);
942 throw new ImportFailure('Wrong Interwiki', 1201);
943 break;
944 case 'cantimport':
945 $this->log('Failed to import page '.$iw.':'.$src.' with error 1203 Forbidden', LG_ERROR);
946 throw new ImportFailure('Forbidden', 1203);
947 break;
948 default:
949 $this->log('Failed to import page '.$iw.':'.$src.' with error 1200 Import Failure', LG_ERROR);
950 throw new ImportFailure('Import Failure', 1200);
952 if (isset($resp['import'][0]['revisions']))
953 return $resp['import'][0]['revisions'];
954 $this->log('Failed to import page '.$iw.':'.$src.' with error 1200 Import Failure', LG_ERROR);
955 throw new ImportFailure('Import Failure', 1200);
959 * Imports an XML file
961 * @param string $file File name, checked in function
962 * @return int Number of revisions imported
963 * @throws ImportFailure
965 public function impxml($file) {
966 if (!is_readable($file)) {
967 $this->log('Failed to import file '.$file.' with error 1204 Bad File', LG_ERROR);
968 throw new ImportFailure('Bad File', 1204);
970 if (function_exists('simplexml_load_file')) if (!@simplexml_load_file($file)) {
971 $this->log('Failed to import file '.$file.' with error 1204 Bad File', LG_ERROR);
972 throw new ImportFailure('Bad File', 1204);
974 $query = array(
975 'action' => 'import',
976 'xml' => "@$file",
977 'token' => urldecode($this->imp_token()),
978 'format' => 'php'
980 $ch = curl_init();
981 $cfg = array(
982 CURLOPT_RETURNTRANSFER => true,
983 CURLOPT_COOKIEJAR => 'cookie.txt',
984 CURLOPT_COOKIEFILE => 'cookie.txt',
985 CURLOPT_USERAGENT => $this->useragent,
986 CURLOPT_HEADER => false,
987 CURLOPT_URL => $this->api_url,
988 CURLOPT_POST => true,
989 CURLOPT_POSTFIELDS => $query,
990 CURLOPT_HTTPHEADER => array('Content-Type: multipart/form-data'),
992 curl_setopt_array($ch, $cfg);
993 $resp = curl_exec($ch);
994 if (curl_errno($ch)) var_dump(curl_error($ch));
995 curl_close($ch);
996 $resp = unserialize($resp);
997 //var_dump($resp);
998 if (isset($resp['error'])) switch ($resp['error']['code']) {
999 case 'cantimport-upload':
1000 $this->log('Failed to import file '.$file.' with error 1203 Forbidden', LG_ERROR);
1001 throw new ImportFailure('Forbidden', 1203);
1002 break;
1003 case 'partialupload':
1004 case 'filetoobig':
1005 $this->log('Failed to import file '.$file.' with error 1202 Upload Failure', LG_ERROR);
1006 throw new ImportFailure('Upload Failure', 1202);
1007 break;
1008 case 'notempdir':
1009 case 'cantopenfile':
1010 $this->log('Failed to import file '.$file.' with error 1205 Server Fault', LG_ERROR);
1011 throw new ImportFailure('Server Fault', 1202);
1012 break;
1013 default:
1014 $this->log('Failed to import file '.$file.' with error 1200 Import Failure', LG_ERROR);
1015 throw new ImportFailure('Import Failure', 1200);
1017 if (isset($resp['import'][0]['revisions']))
1018 return $resp['import'][0]['revisions'];
1019 $this->log('Failed to import file '.$file.' with error 1200 Import Failure', LG_ERROR);
1020 throw new ImportFailure('Import Failure', 1200);
1024 * Purge the cache of a page
1026 * @param mixed $page An array of page names or a string of page name
1027 * @return bool True on success and false on failure
1030 public function purge($page) {
1031 $query = 'action=purge&titles=';
1032 if (is_array($page)) {
1033 foreach ($page as &$i) $i = urlencode($i);
1034 $query .= implode('|', $page);
1035 } else $query .= urlencode($page);
1036 $resp = $this->getAPI($query);
1037 //var_dump($resp);
1038 if (isset($resp['error'])) {
1039 if (is_array($page))
1040 $this->log('Failed to purge pages '.implode(', ', $page).' with error 11 Purge Failure', LG_NOTICE);
1041 else
1042 $this->log('Failed to purge page '.$page.' with error 11 Purge Failure', LG_NOTICE);
1043 return false;
1045 return true;
1049 * Exports a page to xml
1051 * @param mixed $page Array of titles in strings or a string of title
1052 * @return mixed An array of ExportedPage or a object of ExportedPage
1053 * @throws BotException
1055 public function export($page) {
1056 if (is_string($page)) {
1057 $resp = $this->getAPI('action=query&titles='.urlencode($page).'&export');
1058 //var_dump($resp);
1059 $i = new ExportedPage;
1060 foreach ($resp['query']['pages'] as $b) {
1061 $i->title = $b['title'];
1062 if (isset($b['missing'])) return $i;
1063 $i->id = $b['pageid'];
1064 $i->ns = $b['ns'];
1065 $i->title = $b['title'];
1067 $i->xml = $resp['query']['export']['*'];
1068 return $i;
1069 } elseif (is_array($page)) {
1070 $i = array();
1071 foreach ($page as $pg)
1072 $i[$pg] = $this->export($pg);
1073 return $i;
1075 $this->log('PHPwikiBot::export() requires an string or an array for argument no. 1!', LG_FATAL);
1076 throw new BotException('Usage Error', 12);
1080 * Exports page(s) to file(s)
1082 * @param array $page Array Page name => file
1083 * @return int Number of pages exported
1086 public function export_file($page) {
1087 $c = 0;
1088 foreach ($page as $p => $f) {
1089 $i = $this->export($p);
1090 if ($i->xml !== NULL) {
1091 if (file_put_contents($f, $i->xml) === false)
1092 $this->log('Failed to export '.$p.' to '.$f);
1093 else ++$c;
1094 } else $this->log('Page '.$p.' doesn\'t exist!!');
1096 return $c;
1101 /* Internal Methods */
1103 * Change a page's content
1105 * @param string $name Page Name
1106 * @param string $newtext Page Content
1107 * @param string $summary Edit Summary
1108 * @param bool $minor Minor Edit
1109 * @param bool $bot Bot Edit
1110 * @param string $force Force Edit
1111 * @return bool Return true on success
1112 * @throws EditFailure
1115 protected function put_page($name, $newtext, $summary, $minor = false, $bot = true, $force = false) {
1116 foreach ($this->editdetails as $key => $value) {
1117 $token = urlencode($value['edittoken']);
1118 $sts = $value['starttimestamp'];
1119 if (isset($this->editdetails[-1])) {
1120 $ts = $sts;
1121 $extra = '&createonly=yes';
1122 } else {
1123 $ts = $value['revisions'][0]['timestamp'];
1124 $extra = '&nocreate=yes';
1127 $newtext = urlencode($newtext);
1128 try {
1129 $rawoldtext = $this->get_page($name, true);
1130 } catch (GetPageFailure $e) {
1131 if ($e->getCode() == 201)
1132 $rawoldtext = '';
1133 else
1134 throw $e;
1136 $oldtext = urlencode($rawoldtext);
1137 $summary = urlencode($summary);
1138 //$md5 = md5($newtext);
1140 if ($newtext == $oldtext) {
1141 //the new content is the same, nothing changes
1142 $this->log('401 Same Content, can\'t update!!!', LG_ERROR);
1143 throw new EditFailure('Same Content', 401);
1145 if ($newtext == '' && !$force) {
1146 //the new content is void, nothing changes
1147 $this->log('402 Blank Content, use $force!!!', LG_ERROR);
1148 throw new EditFailure('Blank Content', 402);
1150 $post = "title=$name&action=edit&basetimestamp=$ts&starttimestamp=$sts&token=$token&summary=$summary$extra&text=$newtext";
1151 if ($bot) {
1152 if (!$this->allowBots($rawoldtext)) throw new EditFailure('Forbidden', 403);
1153 $post .= '&bot=yes';
1155 if ($minor)
1156 $post .= '&minor=yes';
1157 else
1158 $post .= '&notminor=yes';
1160 $response = $this->postAPI($post);
1161 if (isset($response['edit']['result']) && $response['edit']['result'] == 'Success') {
1162 $this->log('Successfully edited page ' . $response['edit']['title'], LG_INFO);
1163 sleep($this->epm);
1164 return true;
1165 /*Being worked on to throw the right exception*/
1166 } elseif (isset($response['error'])) {
1167 $this->log('[' . $response['error']['code'] . '] ' . $response['error']['info'], LG_ERROR);
1168 switch ($response['error']['code']):
1169 case 'cantcreate':
1170 case 'permissiondenied':
1171 case 'noedit': // 403 Forbidden
1172 throw new EditFailure('Forbidden', 403);
1173 break;
1174 case 'blocked':
1175 case 'autoblocked': // 404 Blocked
1176 throw new EditFailure('Blocked', 404);
1177 break;
1178 case 'protectedtitle':
1179 case 'protectedpage':
1180 case 'protectednamespace':
1181 throw new EditFailure('Protected', 405);
1182 break;
1183 case 'badmd5':
1184 throw new EditFailure('MD5 Failed', 406);
1185 break;
1186 default:
1187 throw new EditFailure('Edit Failure', 400);
1188 endswitch;
1189 } else {
1190 $this->log('[' . $response['edit']['result'] . '] ' . $response['error']['info'], LG_ERROR);
1191 throw EditFailure('Edit Failure', 400);
1196 * Fetch the import token
1198 * @return string urlencode()ed version of token
1201 protected function imp_token() {
1202 $resp = $this->postAPI('action=query&prop=info&intoken=import&titles=Main%20Page');
1203 //var_dump($resp);
1204 if (isset($resp['warnings']['info']['*']) && strstr($resp['warnings']['info']['*'], 'not allowed'))
1205 throw new ImportFailure('Forbidden', 1203);
1206 foreach ($resp['query']['pages'] as $v)
1207 return urlencode($v['importtoken']);
1211 * The login method, used to logon to MediaWiki's API
1213 * @param string $user The username
1214 * @param string $pass The password
1215 * @return bool true when success
1216 * @throws LoginFailure when can't login
1219 protected function login($user, $pass) {
1220 $response = $this->postAPI('action=login&lgname=' . urlencode($user) . '&lgpassword=' . urlencode($pass));
1221 //var_dump($response);
1222 if ($response['login']['result'] == 'Success'):
1223 echo 'Logged in!'.EOL; //Unpatched server, all done. (See bug #23076, April 2010.)
1224 elseif ($response['login']['result'] == 'NeedToken'):
1225 //Patched server, going fine
1226 $token = $response['login']['token'];
1227 $newresponse = $this->postAPI('action=login&lgname=' . urlencode($user) . '&lgpassword=' . urlencode($pass) . '&lgtoken=' . $token);
1228 //var_dump($newresponse);
1229 if ($newresponse['login']['result'] == 'Success') :
1230 echo 'Logged in!'.EOL; //All done
1231 else:
1232 echo 'Forced by server to wait. Automatically trying again.', EOL;
1233 sleep(10);
1234 $this->login($user, $pass);
1235 endif;
1236 else:
1237 //Problem
1238 if (isset($response['login']['wait']) || (isset($response['error']['code']) && $response['error']['code'] == "maxlag")) {
1239 echo 'Forced by server to wait. Automatically trying again.', EOL;
1240 sleep(10);
1241 $this->login($user, $pass);
1242 } else {
1243 // die('Login failed: ' . $response . EOL);
1244 echo 'Debugging Info:', EOL;
1245 var_dump($response);
1246 throw new LoginFailure('Can\'t Login', 100);
1248 endif;
1252 * Logout method, clear all cookies
1254 * @return void There is no such error as can't clear cookies so this was skipped
1257 protected function logout() {
1258 $this->postAPI('action=logout');
1262 * Perform a GET request to the API
1264 * @param string $query The query string to pass the the API, without ?
1265 * @return mixed The unserialized data from the API
1268 protected function getAPI($query) {
1269 //var_dump($this->api_url.'?'.$query.'&maxlag='.$this->max_lag.'&format=php');
1270 curl_setopt($this->get, CURLOPT_URL, $this->api_url.'?'.$query.'&maxlag='.$this->max_lag.'&format=php');
1271 $response = curl_exec($this->get);
1272 if (curl_errno($this->get)) return curl_error($this->get);
1273 /*$fh = fopen('test.txt', 'a');
1274 fwrite($fh, $response);
1275 fclose($fh);*/
1276 //var_dump($response);
1277 return unserialize($response);
1281 * Perform a POST request to the API
1283 * @param string $postdata The data to post in this format a=b&b=c
1284 * @return mixed The unserialized data from the API
1287 protected function postAPI($postdata = '') {
1288 if ($postdata !== '') $postdata .= '&';
1289 $postdata .= 'format=php';
1290 //echo $postdata, EOL;
1291 curl_setopt($this->post, CURLOPT_POSTFIELDS, $postdata);
1292 $response = curl_exec($this->post);
1293 if (curl_errno($this->post)) return curl_error($this->post);
1294 //echo $response, EOL;
1295 //var_dump($response);
1296 return unserialize($response);
1300 * Initiailze the class property, the $get and $post handle
1302 * @return void No return value
1305 protected function conninit() {
1306 $this->get = curl_init();
1307 $this->post = curl_init();
1308 $header = array(
1309 'Connection: keep-alive',
1310 'Keep-Alive: 300'
1312 $cfg = array(
1313 CURLOPT_RETURNTRANSFER => true,
1314 CURLOPT_COOKIEJAR => 'cookie.txt',
1315 CURLOPT_COOKIEFILE => 'cookie.txt',
1316 CURLOPT_USERAGENT => $this->useragent,
1317 CURLOPT_HEADER => false,
1318 CURLOPT_ENCODING => 'gzip,deflate',
1319 CURLOPT_HTTPHEADER => $header,
1321 $header[] = 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8';
1322 $post = array(
1323 CURLOPT_URL => $this->api_url,
1324 CURLOPT_POST => true,
1325 CURLOPT_HTTPHEADER => $header,
1327 curl_setopt_array($this->get, $cfg);
1328 curl_setopt_array($this->post, $cfg);
1329 curl_setopt_array($this->post, $post);
1333 * Log to file and stdout(for html)/stderr(cli)
1335 * @param string $msg Error to log
1336 * @param int $level On of the error constants
1337 * @return void No return value
1340 protected function log($msg, $level = LG_INFO) {
1341 //var_dump($msg, $level, $this->loglevel);
1342 if ($level >= $this->loglevel) {
1343 $msg = date('Y-m-d H:i:s').' - '.$this->loglevelname[$level].': '.$msg;
1344 if ( $this->output_log ) {
1345 if(CLI && LOG_TO_STDERR) {
1346 if ($level < LG_WARN && !WIN32)
1347 fwrite(STDERR, "\033[31m$msg\033[0m".EOL);
1348 else
1349 fwrite(STDERR, $msg.EOL);
1350 } else {
1351 if ($level < LOG_WARN)
1352 echo "\033[31m$msg\033[0m".EOL;
1353 else
1354 echo $msg.EOL;
1357 fwrite($this->logh, $msg.PHP_EOL);
1363 * See if bots are allowed to edit the page
1365 * @param string $text The content of the page
1366 * @return bool Returns true if the bot is allowed
1369 protected function allowBots($text) {
1370 if (preg_match('/\{\{(nobots|bots\|allow=none|bots\|deny=all|bots\|optout=all|bots\|deny=.*?' . preg_quote($this->user, '/') . '.*?)\}\}/iS', $text))
1371 return false;
1372 return true;
1376 * Check if $url is a valid URL, doesn't check if it returns an error when requesting
1378 * @param string $url URL to check
1379 * @return bool True if $url is really a URL or false when it's some what not using HTTP(S) or FTP
1382 protected function is_url($url) {
1383 return (bool)preg_match('/^(http|https|ftp):\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"])*$/', $url);