| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * OO AJAX Implementation for PHP 4 * 5 * SVN Rev: $Id: Server.php,v 1.1.2.1 2008/10/03 07:09:51 nicolasconnault Exp $ 6 * 7 * @category HTML 8 * @package AJAX 9 * @author Joshua Eichorn <josh@bluga.net> 10 * @copyright 2005 Joshua Eichorn 11 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 12 * @version Release: @package_version@ 13 */ 14 15 /** 16 * Require the main AJAX library 17 */ 18 require_once 'HTML/AJAX.php'; 19 20 /** 21 * Class for creating an external AJAX server 22 * 23 * Can be used in 2 different modes, registerClass mode where you create an instance of the server and add the classes that will be registered 24 * and then run handle request 25 * 26 * Or you can extend it and add init{className} methods for each class you want to export 27 * 28 * Client js generation is exposed through 2 _GET params client and stub 29 * Setting the _GET param client to `all` will give you all the js classes needed 30 * Setting the _GET param stub to `all` will give you stubs of all registered classes, you can also set it too just 1 class 31 * 32 * @category HTML 33 * @package AJAX 34 * @author Joshua Eichorn <josh@bluga.net> 35 * @copyright 2005 Joshua Eichorn 36 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 37 * @version Release: @package_version@ 38 * @link http://pear.php.net/package/PackageName 39 */ 40 class HTML_AJAX_Server 41 { 42 43 /** 44 * Client options array if set to true the code looks at _GET 45 * @var bool|array 46 */ 47 var $options = true; 48 49 /** 50 * HTML_AJAX instance 51 * @var HTML_AJAX 52 */ 53 var $ajax; 54 55 /** 56 * Set to true if your extending the server to add init{className methods} 57 * @var boolean 58 * @access public 59 */ 60 var $initMethods = false; 61 62 /** 63 * Location on filesystem of client javascript library 64 * @var false|string if false the default pear data dir location is used 65 */ 66 var $clientJsLocation = false; 67 68 /** 69 * An array of options that tell the server howto Cache output 70 * 71 * The rules are functions that make etag hash used to see if the client needs to download updated content 72 * If you extend this class you can make your own rule function the naming convention is _cacheRule{RuleName} 73 * 74 * <code> 75 * array( 76 * 'httpCacheClient' => true, // send 304 headers for responses to ?client=* requests 77 * 'ClientCacheRule' => 'File', // create a hash from file names and modified times, options: file|content 78 * 'ClientCacheExpects'=> 'files', // what type of content to send to the hash function, options: files|classes|content 79 * 'httpCacheStub' => true, // send 304 headers for responses to ?stub=* requests 80 * 'StubCacheRule' => 'Api', // create a hash from the exposed api, options: api|content 81 * 'StubCacheExpects'=> 'classes', // what type of content to send to the hash function, options: files|classes|content 82 * ) 83 * </code> 84 * 85 * @var array 86 * @access public 87 */ 88 var $cacheOptions = array( 89 'httpCacheClient' => true, 90 'ClientCacheRule' => 'file', 91 'ClientCacheExpects' => 'files', 92 'httpCacheStub' => true, 93 'StubCacheRule' => 'api', 94 'StubCacheExpects' => 'classes', 95 ); 96 97 /** 98 * Compression Options 99 * 100 * <code> 101 * array( 102 * 'enabled' => false, // enable compression 103 * 'type' => 'gzip' // the type of compression to do, options: gzip 104 * ) 105 * </code> 106 * 107 * @var array 108 * @access public 109 */ 110 var $compression = array( 111 'enabled' => false, 112 'type' => 'gzip' 113 ); 114 115 /** 116 * Javascript library names and there path 117 * 118 * the return of $this->clientJsLocation(), is prepended before running readfile on them 119 * 120 * @access public 121 * @var array 122 */ 123 var $javascriptLibraries = array( 124 'all' => 'HTML_AJAX.js', 125 'html_ajax' => 'HTML_AJAX.js', 126 'html_ajax_lite'=> 'HTML_AJAX_lite.js', 127 'json' => 'serializer/JSON.js', 128 'request' => 'Request.js', 129 'main' => array('Compat.js','Main.js','clientPool.js'), 130 'httpclient' => 'HttpClient.js', 131 'dispatcher' => 'Dispatcher.js', 132 'util' => 'util.js', 133 'loading' => 'Loading.js', 134 'phpserializer' => 'serializer/phpSerializer.js', 135 'urlserializer' => 'serializer/UrlSerializer.js', 136 'haserializer' => 'serializer/haSerializer.js', 137 'clientpool' => 'clientPool.js', 138 'iframe' => 'IframeXHR.js', 139 'alias' => 'Alias.js', 140 'queues' => 'Queue.js', 141 'behavior' => array('behavior/behavior.js','behavior/cssQuery-p.js'), 142 143 // rules to help you use a minimal library set 144 'standard' => array('Compat.js','clientPool.js','util.js','Main.js','HttpClient.js','Request.js','serializer/JSON.js', 145 'Loading.js','serializer/UrlSerializer.js','Alias.js','behavior/behavior.js','behavior/cssQuery-p.js'), 146 'jsonrpc' => array('Compat.js','util.js','Main.js','clientPool.js','HttpClient.js','Request.js','serializer/JSON.js'), 147 'proxyobjects' => array('Compat.js','util.js','Main.js','clientPool.js','Request.js','serializer/JSON.js','Dispatcher.js'), 148 149 // BC rules 150 'priorityqueue' => 'Queue.js', 151 'orderedqueue' => 'Queue.js', 152 ); 153 154 /** 155 * Custom paths to use for javascript libraries, if not set {@link clientJsLocation} is used to find the system path 156 * 157 * @access public 158 * @var array 159 * @see registerJsLibrary 160 */ 161 var $javascriptLibraryPaths = array(); 162 163 /** 164 * Array of className => init methods to call, generated from constructor from initClassName methods 165 * 166 * @access protected 167 */ 168 var $_initLookup = array(); 169 170 171 /** 172 * Constructor creates the HTML_AJAX instance 173 * 174 * @param string $serverUrl (Optional) the url the client should be making a request too 175 */ 176 function HTML_AJAX_Server($serverUrl = false) 177 { 178 $this->ajax = new HTML_AJAX(); 179 180 // parameters for HTML::AJAX 181 $parameters = array('stub', 'client'); 182 183 // keep in the query string all the parameters that don't belong to AJAX 184 // we remove all string like "parameter=something&". Final '&' can also 185 // be '&' (to be sure) and is optional. '=something' is optional too. 186 $querystring = ''; 187 if (isset($_SERVER['QUERY_STRING'])) { 188 $querystring = preg_replace('/(' . join('|', $parameters) . ')(?:=[^&]*(?:&(?:amp;)?|$))?/', '', $this->ajax->_getServer('QUERY_STRING')); 189 } 190 191 // call the server with this query string 192 if ($serverUrl === false) { 193 $serverUrl = htmlentities($this->ajax->_getServer('PHP_SELF')); 194 } 195 196 if (substr($serverUrl,-1) != '?') { 197 $serverUrl .= '?'; 198 } 199 $this->ajax->serverUrl = $serverUrl . $querystring; 200 201 $methods = get_class_methods($this); 202 foreach($methods as $method) { 203 if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { 204 $this->_initLookup[strtolower($match[1])] = $method; 205 } 206 } 207 } 208 209 /** 210 * Handle a client request, either generating a client or having HTML_AJAX handle the request 211 * 212 * @return boolean true if request was handled, false otherwise 213 */ 214 function handleRequest() 215 { 216 if ($this->options == true) { 217 $this->_loadOptions(); 218 } 219 //basically a hook for iframe but allows processing of data earlier 220 $this->ajax->populatePayload(); 221 if (!isset($_GET['c']) && (count($this->options['client']) > 0 || count($this->options['stub']) > 0) ) { 222 $this->generateClient(); 223 return true; 224 } else { 225 if (!empty($_GET['c'])) { 226 $this->_init($this->_cleanIdentifier($this->ajax->_getVar('c'))); 227 } 228 return $this->ajax->handleRequest(); 229 } 230 } 231 232 /** 233 * Register method passthrough to HTML_AJAX 234 * 235 * @see HTML_AJAX::registerClass for docs 236 */ 237 function registerClass(&$instance, $exportedName = false, $exportedMethods = false) 238 { 239 $this->ajax->registerClass($instance,$exportedName,$exportedMethods); 240 } 241 242 /** 243 * Change default serialization - important for exporting classes 244 * 245 * I wanted this for the xml serializer :) 246 */ 247 function setSerializer($type) 248 { 249 $this->ajax->serializer = $type; 250 $this->ajax->unserializer = $type; 251 } 252 253 /** 254 * Register a new js client library 255 * 256 * @param string $libraryName name you'll reference the library as 257 * @param string|array $fileName actual filename with no path, for example customLib.js 258 * @param string|false $path Optional, if not set the result from jsClientLocation is used 259 */ 260 function registerJSLibrary($libraryName,$fileName,$path = false) { 261 $libraryName = strtolower($libraryName); 262 $this->javascriptLibraries[$libraryName] = $fileName; 263 264 if ($path !== false) { 265 $this->javascriptLibraryPaths[$libraryName] = $path; 266 } 267 } 268 269 /** 270 * Register init methods from an external class 271 * 272 * @param object $instance an external class with initClassName methods 273 */ 274 function registerInitObject(&$instance) { 275 $instance->server =& $this; 276 $methods = get_class_methods($instance); 277 foreach($methods as $method) { 278 if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { 279 $this->_initLookup[strtolower($match[1])] = array(&$instance,$method); 280 } 281 } 282 } 283 284 /** 285 * Register a callback to be exported to the client 286 * 287 * This function uses the PHP callback pseudo-type 288 * 289 */ 290 function registerPhpCallback($callback) 291 { 292 if (!is_callable($callback)) { 293 // invalid callback 294 return false; 295 } 296 297 if (is_array($callback) && is_object($callback[0])) { 298 // object method 299 $this->registerClass($callback[0], strtolower(get_class($callback[0])), array($callback[1])); 300 return true; 301 } 302 303 // static callback 304 $this->ajax->registerPhpCallback($callback); 305 } 306 307 /** 308 * Generate client js 309 * 310 * @todo this is going to need tests to cover all the options 311 */ 312 function generateClient() 313 { 314 $headers = array(); 315 316 ob_start(); 317 318 // create a list list of js files were going to need to output 319 // index is the full file and so is the value, this keeps duplicates out of $fileList 320 $fileList = array(); 321 322 if(!is_array($this->options['client'])) { 323 $this->options['client'] = array(); 324 } 325 foreach($this->options['client'] as $library) { 326 if (isset($this->javascriptLibraries[$library])) { 327 $lib = (array)$this->javascriptLibraries[$library]; 328 foreach($lib as $file) { 329 if (isset($this->javascriptLibraryPaths[$library])) { 330 $fileList[$this->javascriptLibraryPaths[$library].$file] = $this->javascriptLibraryPaths[$library].$file; 331 } 332 else { 333 $fileList[$this->clientJsLocation().$file] = $this->clientJsLocation().$file; 334 } 335 } 336 } 337 } 338 339 // do needed class init if were running an init server 340 if(!is_array($this->options['stub'])) { 341 $this->options['stub'] = array(); 342 } 343 $classList = $this->options['stub']; 344 if ($this->initMethods) { 345 if (isset($this->options['stub'][0]) && $this->options['stub'][0] === 'all') { 346 $this->_initAll(); 347 } else { 348 foreach($this->options['stub'] as $stub) { 349 $this->_init($stub); 350 } 351 } 352 } 353 if (isset($this->options['stub'][0]) && $this->options['stub'][0] === 'all') { 354 $classList = array_keys($this->ajax->_exportedInstances); 355 } 356 357 // if were doing stub and client we have to wait for both ETags before we can compare with the client 358 $combinedOutput = false; 359 if ($classList != false && count($classList) > 0 && count($fileList) > 0) { 360 $combinedOutput = true; 361 } 362 363 364 if ($classList != false && count($classList) > 0) { 365 366 // were setup enough to make a stubETag if the input it wants is a class list 367 if ($this->cacheOptions['httpCacheStub'] && 368 $this->cacheOptions['StubCacheExpects'] == 'classes') 369 { 370 $stubETag = $this->_callCacheRule('Stub',$classList); 371 } 372 373 // if were not in combined output compare etags, if method returns true were done 374 if (!$combinedOutput && isset($stubETag)) { 375 if ($this->_compareEtags($stubETag)) { 376 ob_end_clean(); 377 return; 378 } 379 } 380 381 // output the stubs for all the classes in our list 382 foreach($classList as $class) { 383 echo $this->ajax->generateClassStub($class); 384 } 385 386 // if were cacheing and the rule expects content make a tag and check it, if the check is true were done 387 if ($this->cacheOptions['httpCacheStub'] && 388 $this->cacheOptions['StubCacheExpects'] == 'content') 389 { 390 $stubETag = $this->_callCacheRule('Stub',ob_get_contents()); 391 } 392 393 // if were not in combined output compare etags, if method returns true were done 394 if (!$combinedOutput && isset($stubETag)) { 395 if ($this->_compareEtags($stubETag)) { 396 ob_end_clean(); 397 return; 398 } 399 } 400 } 401 402 if (count($fileList) > 0) { 403 // if were caching and need a file list build our jsETag 404 if ($this->cacheOptions['httpCacheClient'] && 405 $this->cacheOptions['ClientCacheExpects'] === 'files') 406 { 407 $jsETag = $this->_callCacheRule('Client',$fileList); 408 409 } 410 411 // if were not in combined output compare etags, if method returns true were done 412 if (!$combinedOutput && isset($jsETag)) { 413 if ($this->_compareEtags($jsETag)) { 414 ob_end_clean(); 415 return; 416 } 417 } 418 419 // output the needed client js files 420 foreach($fileList as $file) { 421 $this->_readFile($file); 422 } 423 424 // if were caching and need content build the etag 425 if ($this->cacheOptions['httpCacheClient'] && 426 $this->cacheOptions['ClientCacheExpects'] === 'content') 427 { 428 $jsETag = $this->_callCacheRule('Client',ob_get_contents()); 429 } 430 431 // if were not in combined output compare etags, if method returns true were done 432 if (!$combinedOutput && isset($jsETag)) { 433 if ($this->_compareEtags($jsETag)) { 434 ob_end_clean(); 435 return; 436 } 437 } 438 // were in combined output, merge the 2 ETags and compare 439 else if (isset($jsETag) && isset($stubETag)) { 440 if ($this->_compareEtags(md5($stubETag.$jsETag))) { 441 ob_end_clean(); 442 return; 443 } 444 } 445 } 446 447 448 // were outputting content, add our length header and send the output 449 $length = ob_get_length(); 450 $output = ob_get_contents(); 451 ob_end_clean(); 452 453 if ($this->ajax->packJavaScript) { 454 $output = $this->ajax->packJavaScript($output); 455 $length = strlen($output); 456 } 457 458 if ($this->compression['enabled'] && $this->compression['type'] == 'gzip' && strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip") !== false) { 459 $output = gzencode($output,9); 460 $length = strlen($output); 461 $headers['Content-Encoding'] = 'gzip'; 462 } 463 464 if ($length > 0 && $this->ajax->_sendContentLength()) { 465 $headers['Content-Length'] = $length; 466 } 467 $headers['Content-Type'] = 'text/javascript; charset=utf-8'; 468 $this->ajax->_sendHeaders($headers); 469 echo($output); 470 } 471 472 /** 473 * Run readfile on input with basic error checking 474 * 475 * @param string $file file to read 476 * @access private 477 * @todo is addslashes enough encoding for js? 478 */ 479 function _readFile($file) 480 { 481 if (file_exists($file)) { 482 readfile($file); 483 } else { 484 $file = addslashes($file); 485 echo "alert('Unable to find javascript file: $file');"; 486 } 487 } 488 489 /** 490 * Get the location of the client js 491 * To override the default pear datadir location set $this->clientJsLocation 492 * 493 * @return string 494 */ 495 function clientJsLocation() 496 { 497 if (!$this->clientJsLocation) { 498 $path = '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR; 499 if(strpos($path, '@'.'data-dir@') === 0) 500 { 501 $path = realpath(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'js').DIRECTORY_SEPARATOR; 502 } 503 return $path; 504 } else { 505 return $this->clientJsLocation; 506 } 507 } 508 509 /** 510 * Set the location of the client js 511 * 512 * @access public 513 * @param string $location Location 514 * @return void 515 */ 516 function setClientJsLocation($location) 517 { 518 $this->clientJsLocation = $location; 519 } 520 521 /** 522 * Set the path to a Javascript libraries 523 * 524 * @access public 525 * @param string $library Library name 526 * @param string $path Path 527 * @return void 528 */ 529 function setJavascriptLibraryPath($library, $path) 530 { 531 $this->javascriptLibraryPaths[$library] = $path; 532 } 533 534 /** 535 * Set the path to more than one Javascript libraries at once 536 * 537 * @access public 538 * @param array $paths Paths 539 * @return void 540 */ 541 function setJavascriptLibraryPaths($paths) 542 { 543 if (is_array($paths)) { 544 $this->javascriptLibraryPaths = array_merge($this->javascriptLibraryPaths, $paths); 545 } 546 } 547 548 /** 549 * Load options from _GET 550 * 551 * @access private 552 */ 553 function _loadOptions() 554 { 555 $this->options = array('client'=>array(),'stub'=>array()); 556 if (isset($_GET['client'])) { 557 $clients = explode(',',$this->ajax->_getVar('client')); 558 $client = array(); 559 foreach($clients as $val) { 560 $cleanVal = $this->_cleanIdentifier($val); 561 if (!empty($cleanVal)) { 562 $client[] = strtolower($cleanVal); 563 } 564 } 565 566 if (count($client) > 0) { 567 $this->options['client'] = $client; 568 } 569 } 570 if (isset($_GET['stub'])) { 571 $stubs = explode(',',$this->ajax->_getVar('stub')); 572 $stub = array(); 573 foreach($stubs as $val) { 574 $cleanVal = $this->_cleanIdentifier($val); 575 if (!empty($cleanVal)) { 576 $stub[] = strtolower($cleanVal); 577 } 578 } 579 580 if (count($stub) > 0) { 581 $this->options['stub'] = $stub; 582 } 583 } 584 } 585 586 /** 587 * Clean an identifier like a class name making it safe to use 588 * 589 * @param string $input 590 * @return string 591 * @access private 592 */ 593 function _cleanIdentifier($input) { 594 return trim(preg_replace('/[^A-Za-z_0-9]/','',$input)); 595 } 596 597 /** 598 * Run every init method on the class 599 * 600 * @access private 601 */ 602 function _initAll() 603 { 604 if ($this->initMethods) { 605 foreach($this->_initLookup as $class => $method) { 606 $this->_init($class); 607 } 608 } 609 } 610 611 /** 612 * Init one class 613 * 614 * @param string $className 615 * @access private 616 */ 617 function _init($className) 618 { 619 $className = strtolower($className); 620 if ($this->initMethods) { 621 if (isset($this->_initLookup[$className])) { 622 $method =& $this->_initLookup[$className]; 623 if (is_array($method)) { 624 call_user_func($method); 625 } 626 else { 627 $this->$method(); 628 } 629 } else { 630 trigger_error("Could find an init method for class: " . $className); 631 } 632 } 633 } 634 635 /** 636 * Generate a hash from a list of files 637 * 638 * @param array $files file list 639 * @return string a hash that can be used as an etag 640 * @access private 641 */ 642 function _cacheRuleFile($files) { 643 $signature = ""; 644 foreach($files as $file) { 645 if (file_exists($file)) { 646 $signature .= $file.filemtime($file); 647 } 648 } 649 return md5($signature); 650 } 651 652 /** 653 * Generate a hash from the api of registered classes 654 * 655 * @param array $classes class list 656 * @return string a hash that can be used as an etag 657 * @access private 658 */ 659 function _cacheRuleApi($classes) { 660 $signature = ""; 661 foreach($classes as $class) { 662 if (isset($this->ajax->_exportedInstances[$class])) { 663 $signature .= $class.implode(',',$this->ajax->_exportedInstances[$class]['exportedMethods']); 664 } 665 } 666 return md5($signature); 667 } 668 669 /** 670 * Generate a hash from the raw content 671 * 672 * @param array $content 673 * @return string a hash that can be used as an etag 674 * @access private 675 */ 676 function _cacheRuleContent($content) { 677 return md5($content); 678 } 679 680 /** 681 * Send cache control headers 682 * @access private 683 */ 684 function _sendCacheHeaders($etag,$notModified) { 685 header('Cache-Control: must-revalidate'); 686 header('ETag: '.$etag); 687 if ($notModified) { 688 header('HTTP/1.0 304 Not Modified',false,304); 689 } 690 } 691 692 /** 693 * Compare eTags 694 * 695 * @param string $serverETag server eTag 696 * @return boolean 697 * @access private 698 */ 699 function _compareEtags($serverETag) { 700 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 701 if (strcmp($this->ajax->_getServer('HTTP_IF_NONE_MATCH'),$serverETag) == 0) { 702 $this->_sendCacheHeaders($serverETag,true); 703 return true; 704 } 705 } 706 $this->_sendCacheHeaders($serverETag,false); 707 return false; 708 } 709 710 /** 711 * Call a cache rule and return its retusn 712 * 713 * @param string $rule Stub|Client 714 * @param mixed $payload 715 * @return boolean 716 * @access private 717 * @todo decide if error checking is needed 718 */ 719 function _callCacheRule($rule,$payload) { 720 $method = '_cacheRule'.$this->cacheOptions[$rule.'CacheRule']; 721 return call_user_func(array(&$this,$method),$payload); 722 } 723 } 724 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 725 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |