Overview

Packages

  • Phery

Classes

  • Phery
  • PheryFunction
  • PheryResponse

Exceptions

  • PheryException
  • Overview
  • Package
  • Class
   1: <?php
   2: /**
   3:  * The MIT License (MIT)
   4:  *
   5:  * Copyright © 2010-2013 Paulo Cesar, http://phery-php-ajax.net/
   6:  *
   7:  * Permission is hereby granted, free of charge, to any person
   8:  * obtaining a copy of this software and associated documentation
   9:  * files (the “Software”), to deal in the Software without restriction,
  10:  * including without limitation the rights to use, copy, modify, merge,
  11:  * publish, distribute, sublicense, and/or sell copies of the Software,
  12:  * and to permit persons to whom the Software is furnished to do so,
  13:  * subject to the following conditions:
  14:  *
  15:  * The above copyright notice and this permission notice shall be included
  16:  * in all copies or substantial portions of the Software.
  17:  *
  18:  * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
  19:  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20:  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  21:  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  22:  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  23:  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  24:  * OTHER DEALINGS IN THE SOFTWARE.
  25:  *
  26:  * @link       http://phery-php-ajax.net/
  27:  * @author     Paulo Cesar
  28:  * @version    2.7.2
  29:  * @license    http://opensource.org/licenses/MIT MIT License
  30:  */
  31: 
  32: /**
  33:  * Main class for Phery.js
  34:  *
  35:  * @package    Phery
  36:  */
  37: class Phery implements ArrayAccess {
  38: 
  39:     /**
  40:      * Exception on callback() function
  41:      * @see callback()
  42:      * @type int
  43:      */
  44:     const ERROR_CALLBACK = 0;
  45:     /**
  46:      * Exception on process() function
  47:      * @see process()
  48:      */
  49:     const ERROR_PROCESS = 1;
  50:     /**
  51:      * Exception on set() function
  52:      * @see set()
  53:      */
  54:     const ERROR_SET = 2;
  55:     /**
  56:      * Exception when the CSRF is invalid
  57:      * @see process()
  58:      */
  59:     const ERROR_CSRF = 4;
  60:     /**
  61:      * Exception on static functions
  62:      * @see link_to()
  63:      * @see select_for()
  64:      * @see form_for()
  65:      */
  66:     const ERROR_TO = 3;
  67:     /**
  68:      * Default encoding for your application
  69:      * @var string
  70:      */
  71:     public static $encoding = 'UTF-8';
  72:     /**
  73:      * Expose the paths on PheryResponse exceptions
  74:      * @var bool
  75:      */
  76:     public static $expose_paths = false;
  77:     /**
  78:      * The functions registered
  79:      * @var array
  80:      */
  81:     protected $functions = array();
  82:     /**
  83:      * The callbacks registered
  84:      * @var array
  85:      */
  86:     protected $callbacks = array();
  87:     /**
  88:      * The callback data to be passed to callbacks and responses
  89:      * @var array
  90:      */
  91:     protected $data = array();
  92:     /**
  93:      * Static instance for singleton
  94:      * @var Phery
  95:      * @static
  96:      */
  97:     protected static $instance = null;
  98:     /**
  99:      * Render view function
 100:      * @var array
 101:      */
 102:     protected $views = array();
 103:     /**
 104:      * Config
 105:      *
 106:      * <code>
 107:      * 'exit_allowed' (boolean)
 108:      * 'exceptions' (boolean)
 109:      * 'return' (boolean)
 110:      * 'error_reporting' (int)
 111:      * 'csrf' (boolean)
 112:      * 'set_always_available' (boolean)
 113:      * 'auto_session' (boolean)
 114:      * </code>
 115:      * @var array
 116:      *
 117:      * @see config()
 118:      */
 119:     protected $config = array();
 120:     /**
 121:      * If the class was just initiated
 122:      * @var bool
 123:      */
 124:     private $init = true;
 125: 
 126:     /**
 127:      * Construct the new Phery instance
 128:      * @param array $config Config array
 129:      */
 130:     public function __construct(array $config = array())
 131:     {
 132:         $this->callbacks = array(
 133:             'before' => array(),
 134:             'after' => array()
 135:         );
 136: 
 137:         $config = array_replace(
 138:             array(
 139:                 'exit_allowed' => true,
 140:                 'exceptions' => false,
 141:                 'return' => false,
 142:                 'csrf' => false,
 143:                 'set_always_available' => false,
 144:                 'error_reporting' => false,
 145:                 'auto_session' => true,
 146:             ), $config
 147:         );
 148: 
 149:         $this->config($config);
 150:     }
 151: 
 152:     /**
 153:      * Set callbacks for before and after filters.
 154:      * Callbacks are useful for example, if you have 2 or more AJAX functions, and you need to perform
 155:      * the same data manipulation, like removing an 'id' from the $_POST['args'], or to check for potential
 156:      * CSRF or SQL injection attempts on all the functions, clean data or perform START TRANSACTION for database, etc
 157:      *
 158:      * @param array $callbacks The callbacks
 159:      *
 160:      * <pre>
 161:      * array(
 162:      *
 163:      *     // Set a function to be called BEFORE
 164:      *     // processing the request, if it's an
 165:      *     // AJAX to be processed request, can be
 166:      *     // an array of callbacks
 167:      *
 168:      *     'before' => array|function,
 169:      *
 170:      *     // Set a function to be called AFTER
 171:      *     // processing the request, if it's an AJAX
 172:      *     // processed request, can be an array of
 173:      *     // callbacks
 174:      *
 175:      *     'after' => array|function
 176:      * );
 177:      * </pre>
 178:      *
 179:      * The callback function should be
 180:      *
 181:      * <pre>
 182:      *
 183:      * // $additional_args is passed using the callback_data() function,
 184:      * // in this case, a before callback
 185:      *
 186:      * function before_callback($ajax_data, $internal_data){
 187:      *   // Do stuff
 188:      *   $_POST['args']['id'] = $additional_args['id'];
 189:      *   return true;
 190:      * }
 191:      *
 192:      * // after callback would be to save the data perhaps? Just to keep the code D.R.Y.
 193:      *
 194:      * function after_callback($ajax_data, $internal_data, $PheryResponse){
 195:      *   $this->database->save();
 196:      *   $PheryResponse->merge(PheryResponse::factory('#loading')->fadeOut());
 197:      *   return true;
 198:      * }
 199:      * </pre>
 200:      *
 201:      * Returning false on the callback will make the process() phase to RETURN, but won't exit.
 202:      * You may manually exit on the after callback if desired
 203:      * Any data that should be modified will be inside $_POST['args'] (can be accessed freely on 'before',
 204:      * will be passed to the AJAX function)
 205:      *
 206:      * @return Phery
 207:      */
 208:     public function callback(array $callbacks)
 209:     {
 210:         if (isset($callbacks['before']))
 211:         {
 212:             if (is_array($callbacks['before']) && !is_callable($callbacks['before']))
 213:             {
 214:                 foreach ($callbacks['before'] as $func)
 215:                 {
 216:                     if (is_callable($func))
 217:                     {
 218:                         $this->callbacks['before'][] = $func;
 219:                     }
 220:                     else
 221:                     {
 222:                         self::exception($this, "The provided before callback function isn't callable", self::ERROR_CALLBACK);
 223:                     }
 224:                 }
 225:             }
 226:             else
 227:             {
 228:                 if (is_callable($callbacks['before']))
 229:                 {
 230:                     $this->callbacks['before'][] = $callbacks['before'];
 231:                 }
 232:                 else
 233:                 {
 234:                     self::exception($this, "The provided before callback function isn't callable", self::ERROR_CALLBACK);
 235:                 }
 236:             }
 237:         }
 238: 
 239:         if (isset($callbacks['after']))
 240:         {
 241:             if (is_array($callbacks['after']) && !is_callable($callbacks['after']))
 242:             {
 243: 
 244:                 foreach ($callbacks['after'] as $func)
 245:                 {
 246:                     if (is_callable($func))
 247:                     {
 248:                         $this->callbacks['after'][] = $func;
 249:                     }
 250:                     else
 251:                     {
 252:                         self::exception($this, "The provided after callback function isn't callable", self::ERROR_CALLBACK);
 253:                     }
 254:                 }
 255:             }
 256:             else
 257:             {
 258:                 if (is_callable($callbacks['after']))
 259:                 {
 260:                     $this->callbacks['after'][] = $callbacks['after'];
 261:                 }
 262:                 else
 263:                 {
 264:                     self::exception($this, "The provided after callback function isn't callable", self::ERROR_CALLBACK);
 265:                 }
 266:             }
 267:         }
 268: 
 269:         return $this;
 270:     }
 271: 
 272:     /**
 273:      * Throw an exception if enabled
 274:      *
 275:      * @param Phery   $phery Instance
 276:      * @param string  $exception
 277:      * @param integer $code
 278:      *
 279:      * @throws PheryException
 280:      * @return boolean
 281:      */
 282:     protected static function exception($phery, $exception, $code)
 283:     {
 284:         if ($phery instanceof Phery && $phery->config['exceptions'] === true)
 285:         {
 286:             throw new PheryException($exception, $code);
 287:         }
 288: 
 289:         return false;
 290:     }
 291: 
 292: 
 293: 
 294:     /**
 295:      * Set any data to pass to the callbacks
 296:      *
 297:      * @param mixed $args,... Parameters, can be anything
 298:      *
 299:      * @return Phery
 300:      */
 301:     public function data($args)
 302:     {
 303:         foreach (func_get_args() as $arg)
 304:         {
 305:             if (is_array($arg))
 306:             {
 307:                 $this->data = array_merge_recursive($arg, $this->data);
 308:             }
 309:             else
 310:             {
 311:                 $this->data[] = $arg;
 312:             }
 313:         }
 314: 
 315:         return $this;
 316:     }
 317: 
 318:     /**
 319:      * Encode PHP code to put inside data-phery-args, usually for updating the data there
 320:      *
 321:      * @param array  $data     Any data that can be converted using json_encode
 322:      * @param string $encoding Encoding for the arguments
 323:      *
 324:      * @return string Return json_encode'd and htmlentities'd string
 325:      */
 326:     public static function args(array $data, $encoding = 'UTF-8')
 327:     {
 328:         return htmlentities(json_encode($data), ENT_COMPAT, $encoding, false);
 329:     }
 330: 
 331:     /**
 332:      * Get the current token from the $_SESSION
 333:      *
 334:      * @return bool
 335:      */
 336:     public function get_csrf_token()
 337:     {
 338:         if (!empty($_SESSION['phery']['csrf']))
 339:         {
 340:             return $_SESSION['phery']['csrf'];
 341:         }
 342: 
 343:         return false;
 344:     }
 345: 
 346:     /**
 347:      * Output the meta HTML with the token.
 348:      * This method needs to use sessions through session_start
 349:      *
 350:      * @param bool $check Check if the current token is valid
 351:      * @param bool $force It will renew the current hash every call
 352:      * @return string|bool
 353:      */
 354:     public function csrf($check = false, $force = false)
 355:     {
 356:         if ($this->config['csrf'] !== true)
 357:         {
 358:             return !empty($check) ? true : '';
 359:         }
 360: 
 361:         if (session_id() == '' && $this->config['auto_session'] === true)
 362:         {
 363:             @session_start();
 364:         }
 365: 
 366:         if ($check === false)
 367:         {
 368:             $current_token = $this->get_csrf_token();
 369: 
 370:             if (($current_token !== false && $force) || $current_token === false)
 371:             {
 372:                 $token = sha1(uniqid(microtime(true), true));
 373: 
 374:                 $_SESSION['phery'] = array(
 375:                     'csrf' => $token
 376:                 );
 377: 
 378:                 $token = base64_encode($token);
 379:             }
 380:             else
 381:             {
 382:                 $token = base64_encode($_SESSION['phery']['csrf']);
 383:             }
 384: 
 385:             return "<meta id=\"csrf-token\" name=\"csrf-token\" content=\"{$token}\" />\n";
 386:         }
 387:         else
 388:         {
 389:             if (empty($_SESSION['phery']['csrf']))
 390:             {
 391:                 return false;
 392:             }
 393: 
 394:             return $_SESSION['phery']['csrf'] === base64_decode($check, true);
 395:         }
 396:     }
 397: 
 398:     /**
 399:      * Check if the current call is an ajax call
 400:      *
 401:      * @param bool $is_phery Check if is an ajax call and a phery specific call
 402:      *
 403:      * @static
 404:      * @return bool
 405:      */
 406:     public static function is_ajax($is_phery = false)
 407:     {
 408:         switch ($is_phery)
 409:         {
 410:             case true:
 411:                 return (bool)(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
 412:                 strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') === 0 &&
 413:                 strtoupper($_SERVER['REQUEST_METHOD']) === 'POST' &&
 414:                 !empty($_SERVER['HTTP_X_PHERY']));
 415:             case false:
 416:                 return (bool)(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
 417:                 strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') === 0);
 418:         }
 419:         return false;
 420:     }
 421: 
 422:     /**
 423:      * Strip slashes recursive
 424:      *
 425:      * @param array|string $variable
 426:      * @return array|string
 427:      */
 428:     private function stripslashes_recursive($variable)
 429:     {
 430:         if (!empty($variable) && is_string($variable))
 431:         {
 432:             return stripslashes($variable);
 433:         }
 434: 
 435:         if (!empty($variable) && is_array($variable))
 436:         {
 437:             foreach ($variable as $i => $value)
 438:             {
 439:                 $variable[$i] = $this->stripslashes_recursive($value);
 440:             }
 441:         }
 442: 
 443:         return $variable;
 444:     }
 445: 
 446:     /**
 447:      * Flush loop
 448:      *
 449:      * @param bool $clean Discard buffers
 450:      */
 451:     private static function flush($clean = false)
 452:     {
 453:         while (ob_get_level() > 0)
 454:         {
 455:             $clean ? ob_end_clean() : ob_end_flush();
 456:         }
 457:     }
 458: 
 459:     /**
 460:      * Default error handler
 461:      *
 462:      * @param int $errno
 463:      * @param string $errstr
 464:      * @param string $errfile
 465:      * @param int $errline
 466:      */
 467:     public static function error_handler($errno, $errstr, $errfile, $errline)
 468:     {
 469:         self::flush(true);
 470: 
 471:         $response = PheryResponse::factory()->exception($errstr, array(
 472:             'code' => $errno,
 473:             'file' => Phery::$expose_paths ? $errfile : pathinfo($errfile, PATHINFO_BASENAME),
 474:             'line' => $errline
 475:         ));
 476: 
 477:         self::respond($response);
 478:         self::shutdown_handler(false, true);
 479:     }
 480: 
 481:     /**
 482:      * Default shutdown handler
 483:      *
 484:      * @param bool $errors
 485:      * @param bool $handled
 486:      */
 487:     public static function shutdown_handler($errors = false, $handled = false)
 488:     {
 489:         if ($handled)
 490:         {
 491:             self::flush();
 492:         }
 493: 
 494:         if ($errors === true && ($error = error_get_last()) && !$handled)
 495:         {
 496:             self::error_handler($error["type"], $error["message"], $error["file"], $error["line"]);
 497:         }
 498: 
 499:         if (!$handled)
 500:         {
 501:             self::flush();
 502:         }
 503: 
 504:         if (session_id() != '')
 505:         {
 506:             session_write_close();
 507:         }
 508: 
 509:         exit;
 510:     }
 511: 
 512:     /**
 513:      * Helper function to properly output the headers for a PheryResponse in case you need
 514:      * to manually return it (like when following a redirect)
 515:      *
 516:      * @param string|PheryResponse $response The response or a string
 517:      * @param bool                 $echo     Echo the response
 518:      *
 519:      * @return string
 520:      */
 521:     public static function respond($response, $echo = true)
 522:     {
 523:         if ($response instanceof PheryResponse)
 524:         {
 525:             if (!headers_sent())
 526:             {
 527:                 if (session_id() != '') {
 528:                     session_write_close();
 529:                 }
 530: 
 531:                 header('Cache-Control: no-cache, must-revalidate', true);
 532:                 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT', true);
 533:                 header('Content-Type: application/json; charset='.(strtolower(Phery::$encoding)), true);
 534:                 header('Connection: close', true);
 535:             }
 536:         }
 537: 
 538:         if ($response)
 539:         {
 540:             $response = "{$response}";
 541:         }
 542: 
 543:         if ($echo === true)
 544:         {
 545:             echo $response;
 546:         }
 547: 
 548:         return $response;
 549:     }
 550: 
 551:     /**
 552:      * Set the callback for view portions, as defined in Phery.view()
 553:      *
 554:      * @param array $views Array consisting of array('#id_of_view' => callback)
 555:      *                     The callback is like a normal phery callback, but the second parameter
 556:      *                     receives different data. But it MUST always return a PheryResponse with
 557:      *                     render_view(). You can do any manipulation like you would in regular
 558:      *                     callbacks. If you want to manipulate the DOM AFTER it was rendered, do it
 559:      *                     javascript side, using the afterHtml callback when setting up the views.
 560:      *
 561:      * <pre>
 562:      * Phery::instance()->views(array(
 563:      *     'section#container' => function($data, $params){
 564:      *          return
 565:      *              PheryResponse::factory()
 566:      *              ->render_view('html', array('extra data like titles, menus, etc'));
 567:      *      }
 568:      * ));
 569:      * </pre>
 570:      *
 571:      * @return Phery
 572:      */
 573:     public function views(array $views)
 574:     {
 575:         foreach ($views as $container => $callback)
 576:         {
 577:             if (is_callable($callback))
 578:             {
 579:                 $this->views[$container] = $callback;
 580:             }
 581:         }
 582: 
 583:         return $this;
 584:     }
 585: 
 586:     /**
 587:      * Initialize stuff before calling the AJAX function
 588:      *
 589:      * @return void
 590:      */
 591:     protected function before_user_func()
 592:     {
 593:         if ($this->config['error_reporting'] !== false)
 594:         {
 595:             set_error_handler('Phery::error_handler', $this->config['error_reporting']);
 596:         }
 597: 
 598:         if (empty($_POST['phery']['csrf']))
 599:         {
 600:             $_POST['phery']['csrf'] = '';
 601:         }
 602: 
 603:         if ($this->csrf($_POST['phery']['csrf']) === false)
 604:         {
 605:             self::exception($this, 'Invalid CSRF token', self::ERROR_CSRF);
 606:         }
 607:     }
 608: 
 609:     /**
 610:      * Process the requests if any
 611:      *
 612:      * @param boolean $last_call
 613:      *
 614:      * @return boolean
 615:      */
 616:     private function process_data($last_call)
 617:     {
 618:         $response = null;
 619:         $error = null;
 620:         $view = false;
 621: 
 622:         if (empty($_POST['phery']))
 623:         {
 624:             return self::exception($this, 'Non-Phery AJAX request', self::ERROR_PROCESS);
 625:         }
 626: 
 627:         if (!empty($_GET['_']))
 628:         {
 629:             $this->data['requested'] = (int)$_GET['_'];
 630:             unset($_GET['_']);
 631:         }
 632: 
 633:         if (isset($_GET['_try_count']))
 634:         {
 635:             $this->data['retries'] = (int)$_GET['_try_count'];
 636:             unset($_GET['_try_count']);
 637:         }
 638: 
 639:         $args = array();
 640:         $remote = false;
 641: 
 642:         if (!empty($_POST['phery']['remote']))
 643:         {
 644:             $remote = $_POST['phery']['remote'];
 645:         }
 646: 
 647:         if (!empty($_POST['phery']['submit_id']))
 648:         {
 649:             $this->data['submit_id'] = "#{$_POST['phery']['submit_id']}";
 650:         }
 651: 
 652:         if ($remote !== false)
 653:         {
 654:             $this->data['remote'] = $remote;
 655:         }
 656: 
 657:         if (!empty($_POST['args']))
 658:         {
 659:             $args = get_magic_quotes_gpc() ? $this->stripslashes_recursive($_POST['args']) : $_POST['args'];
 660: 
 661:             if ($last_call === true)
 662:             {
 663:                 unset($_POST['args']);
 664:             }
 665:         }
 666: 
 667:         foreach ($_POST['phery'] as $name => $post)
 668:         {
 669:             if (!isset($this->data[$name]))
 670:             {
 671:                 $this->data[$name] = $post;
 672:             }
 673:         }
 674: 
 675:         if (count($this->callbacks['before']))
 676:         {
 677:             foreach ($this->callbacks['before'] as $func)
 678:             {
 679:                 if (($args = call_user_func($func, $args, $this->data, $this)) === false)
 680:                 {
 681:                     return false;
 682:                 }
 683:             }
 684:         }
 685: 
 686:         if (!empty($_POST['phery']['view']))
 687:         {
 688:             $this->data['view'] = $_POST['phery']['view'];
 689:         }
 690: 
 691:         if ($remote !== false)
 692:         {
 693:             if (isset($this->functions[$remote]))
 694:             {
 695:                 if (isset($_POST['phery']['remote']))
 696:                 {
 697:                     unset($_POST['phery']['remote']);
 698:                 }
 699: 
 700:                 $this->before_user_func();
 701: 
 702:                 $response = call_user_func($this->functions[$remote], $args, $this->data, $this);
 703: 
 704:                 foreach ($this->callbacks['after'] as $func)
 705:                 {
 706:                     if (call_user_func($func, $args, $this->data, $response, $this) === false)
 707:                     {
 708:                         return false;
 709:                     }
 710:                 }
 711: 
 712:                 if (($response = self::respond($response, false)) === null)
 713:                 {
 714:                     $error = 'Response was void for function "'. htmlentities($remote, ENT_COMPAT, null, false). '"';
 715:                 }
 716: 
 717:                 $_POST['phery']['remote'] = $remote;
 718:             }
 719:             else
 720:             {
 721:                 if ($last_call)
 722:                 {
 723:                     self::exception($this, 'The function provided "' . htmlentities($remote, ENT_COMPAT, null, false) . '" isn\'t set', self::ERROR_PROCESS);
 724:                 }
 725:             }
 726:         }
 727:         else
 728:         {
 729:             if (!empty($this->data['view']) && isset($this->views[$this->data['view']]))
 730:             {
 731:                 $view = $this->data['view'];
 732: 
 733:                 $this->before_user_func();
 734: 
 735:                 $response = call_user_func($this->views[$this->data['view']], $args, $this->data, $this);
 736: 
 737:                 foreach ($this->callbacks['after'] as $func)
 738:                 {
 739:                     if (call_user_func($func, $args, $this->data, $response, $this) === false)
 740:                     {
 741:                         return false;
 742:                     }
 743:                 }
 744: 
 745:                 if (($response = self::respond($response, false)) === null)
 746:                 {
 747:                     $error = 'Response was void for view "'. htmlentities($this->data['view'], ENT_COMPAT, null, false) . '"';
 748:                 }
 749:             }
 750:             else
 751:             {
 752:                 if ($last_call)
 753:                 {
 754:                     if (!empty($this->data['view']))
 755:                     {
 756:                         self::exception($this, 'The provided view "' . htmlentities($this->data['view'], ENT_COMPAT, null, false) . '" isn\'t set', self::ERROR_PROCESS);
 757:                     }
 758:                     else
 759:                     {
 760:                         self::exception($this, 'Empty request', self::ERROR_PROCESS);
 761:                     }
 762:                 }
 763:             }
 764:         }
 765: 
 766:         if ($error !== null)
 767:         {
 768:             self::error_handler(E_NOTICE, $error, '', 0);
 769:         }
 770:         elseif ($response === null && $last_call & !$view)
 771:         {
 772:             $response = PheryResponse::factory();
 773:         }
 774:         elseif ($response !== null)
 775:         {
 776:             ob_start();
 777: 
 778:             if (!$this->config['return'])
 779:             {
 780:                 echo $response;
 781:             }
 782:         }
 783: 
 784:         if (!$this->config['return'] && $this->config['exit_allowed'] === true)
 785:         {
 786:             if ($last_call || $response !== null)
 787:             {
 788:                 exit;
 789:             }
 790:         }
 791:         elseif ($this->config['return'])
 792:         {
 793:             self::flush(true);
 794:         }
 795: 
 796:         if ($this->config['error_reporting'] !== false)
 797:         {
 798:             restore_error_handler();
 799:         }
 800: 
 801:         return $response;
 802:     }
 803: 
 804:     /**
 805:      * Process the AJAX requests if any.
 806:      *
 807:      * @param bool $last_call Set this to false if any other further calls
 808:      *                        to process() will happen, otherwise it will exit
 809:      *
 810:      * @throws PheryException
 811:      * @return boolean Return false if any error happened
 812:      */
 813:     public function process($last_call = true)
 814:     {
 815:         if (self::is_ajax(true))
 816:         {
 817:             // AJAX call
 818:             return $this->process_data($last_call);
 819:         }
 820:         return true;
 821:     }
 822: 
 823:     /**
 824:      * Config the current instance of Phery
 825:      *
 826:      * <code>
 827:      * array(
 828:      *     // Defaults to true, stop further script execution
 829:      *     'exit_allowed' => true|false,
 830:      *
 831:      *     // Throw exceptions on errors
 832:      *     'exceptions' => true|false,
 833:      *
 834:      *     // Return the responses in the process() call instead of echo'ing
 835:      *     'return' => true|false,
 836:      *
 837:      *     // Error reporting temporarily using error_reporting(). 'false' disables
 838:      *     // the error_reporting and wont try to catch any error.
 839:      *     // Anything else than false will throw a PheryResponse->exception() with
 840:      *     // the message
 841:      *     'error_reporting' => false|E_ALL|E_DEPRECATED|...
 842:      *
 843:      *     // By default, the function Phery::instance()->set() will only
 844:      *     // register functions when the current request is an AJAX call,
 845:      *     // to save resources. In order to use Phery::instance()->get_function()
 846:      *     // anytime, you need to set this config value to true
 847:      *     'set_always_available' => false|true
 848:      * );
 849:      * </code>
 850:      *
 851:      * If you pass a string, it will return the current config for the key specified
 852:      * Anything else, will output the current config as associative array
 853:      *
 854:      * @param string|array $config Associative array containing the following options
 855:      *
 856:      * @return Phery|string|array
 857:      */
 858:     public function config($config = null)
 859:     {
 860:         $register_function = false;
 861: 
 862:         if (!empty($config))
 863:         {
 864:             if (is_array($config))
 865:             {
 866:                 if (isset($config['exit_allowed']))
 867:                 {
 868:                     $this->config['exit_allowed'] = (bool)$config['exit_allowed'];
 869:                 }
 870: 
 871:                 if (isset($config['auto_session']))
 872:                 {
 873:                     $this->config['auto_session'] = (bool)$config['auto_session'];
 874:                 }
 875: 
 876:                 if (isset($config['return']))
 877:                 {
 878:                     $this->config['return'] = (bool)$config['return'];
 879:                 }
 880: 
 881:                 if (isset($config['set_always_available']))
 882:                 {
 883:                     $this->config['set_always_available'] = (bool)$config['set_always_available'];
 884:                 }
 885: 
 886:                 if (isset($config['exceptions']))
 887:                 {
 888:                     $this->config['exceptions'] = (bool)$config['exceptions'];
 889:                 }
 890: 
 891:                 if (isset($config['csrf']))
 892:                 {
 893:                     $this->config['csrf'] = (bool)$config['csrf'];
 894:                 }
 895: 
 896:                 if (isset($config['error_reporting']))
 897:                 {
 898:                     if ($config['error_reporting'] !== false)
 899:                     {
 900:                         $this->config['error_reporting'] = (int)$config['error_reporting'];
 901:                     }
 902:                     else
 903:                     {
 904:                         $this->config['error_reporting'] = false;
 905:                     }
 906: 
 907:                     $register_function = true;
 908:                 }
 909: 
 910:                 if ($register_function || $this->init)
 911:                 {
 912:                     register_shutdown_function('Phery::shutdown_handler', $this->config['error_reporting'] !== false);
 913:                     $this->init = false;
 914:                 }
 915: 
 916:                 return $this;
 917:             }
 918:             elseif (!empty($config) && is_string($config) && isset($this->config[$config]))
 919:             {
 920:                 return $this->config[$config];
 921:             }
 922:         }
 923: 
 924:         return $this->config;
 925:     }
 926: 
 927:     /**
 928:      * Generates just one instance. Useful to use in many included files. Chainable
 929:      *
 930:      * @param array $config Associative config array
 931:      *
 932:      * @see __construct()
 933:      * @see config()
 934:      * @static
 935:      * @return Phery
 936:      */
 937:     public static function instance(array $config = array())
 938:     {
 939:         if (!(self::$instance instanceof Phery))
 940:         {
 941:             self::$instance = new Phery($config);
 942:         }
 943:         else if ($config)
 944:         {
 945:             self::$instance->config($config);
 946:         }
 947: 
 948:         return self::$instance;
 949:     }
 950: 
 951:     /**
 952:      * Sets the functions to respond to the ajax call.
 953:      * For security reasons, these functions should not be reacheable through POST/GET requests.
 954:      * These will be set only for AJAX requests as it will only be set in case of an ajax request,
 955:      * to save resources.
 956:      *
 957:      * You may set the config option "set_always_available" to true to always register the functions
 958:      * regardless of if it's an AJAX function or not going on.
 959:      *
 960:      * The answer/process function, should have the following structure:
 961:      *
 962:      * <code>
 963:      * function func($ajax_data, $callback_data, $phery){
 964:      *   $r = new PheryResponse; // or PheryResponse::factory();
 965:      *
 966:      *   // Sometimes the $callback_data will have an item called 'submit_id',
 967:      *   // is the ID of the calling DOM element.
 968:      *   // if (isset($callback_data['submit_id'])) {  }
 969:      *   // $phery will be the current phery instance that called this callback
 970:      *
 971:      *   $r->jquery('#id')->animate(...);
 972:      *   return $r; //Should always return the PheryResponse unless you are dealing with plain text
 973:      * }
 974:      * </code>
 975:      *
 976:      * @param array $functions An array of functions to register to the instance.
 977:      * <pre>
 978:      * array(
 979:      *   'function1' => 'function',
 980:      *   'function2' => array($this, 'method'),
 981:      *   'function3' => 'StaticClass::name',
 982:      *   'function4' => array(new ClassName, 'method'),
 983:      *   'function5' => function($data){}
 984:      * );
 985:      * </pre>
 986:      * @return Phery
 987:      */
 988:     public function set(array $functions)
 989:     {
 990:         if ($this->config['set_always_available'] === false && !self::is_ajax(true))
 991:         {
 992:             return $this;
 993:         }
 994: 
 995:         if (isset($functions) && is_array($functions))
 996:         {
 997:             foreach ($functions as $name => $func)
 998:             {
 999:                 if (is_callable($func))
1000:                 {
1001:                     $this->functions[$name] = $func;
1002:                 }
1003:                 else
1004:                 {
1005:                     self::exception($this, 'Provided function "' . $name . '" isnt a valid function or method', self::ERROR_SET);
1006:                 }
1007:             }
1008:         }
1009:         else
1010:         {
1011:             self::exception($this, 'Call to "set" must be provided an array', self::ERROR_SET);
1012:         }
1013: 
1014:         return $this;
1015:     }
1016: 
1017:     /**
1018:      * Unset a function previously set with set()
1019:      *
1020:      * @param string $name Name of the function
1021:      * @see set()
1022:      * @return Phery
1023:      */
1024:     public function unset_function($name)
1025:     {
1026:         if (isset($this->functions[$name]))
1027:         {
1028:             unset($this->functions[$name]);
1029:         }
1030:         return $this;
1031:     }
1032: 
1033:     /**
1034:      * Get previously function set with set() method
1035:      * If you pass aditional arguments, the function will be executed
1036:      * and this function will return the PheryResponse associated with
1037:      * that function
1038:      *
1039:      * <pre>
1040:      * Phery::get_function('render', ['<html></html>'])->appendTo('body');
1041:      * </pre>
1042:      *
1043:      * @param string $function_name The name of the function registed with set
1044:      * @param array $args Any arguments to pass to the function
1045:      * @see Phery::set()
1046:      * @return callable|array|string|PheryResponse|null
1047:      */
1048:     public function get_function($function_name, array $args = array())
1049:     {
1050:         if (isset($this->functions[$function_name]))
1051:         {
1052:             if (count($args))
1053:             {
1054:                 return call_user_func_array($this->functions[$function_name], $args);
1055:             }
1056: 
1057:             return $this->functions[$function_name];
1058:         }
1059:         return null;
1060:     }
1061: 
1062:     /**
1063:      * Create a new instance of Phery that can be chained, without the need of assigning it to a variable
1064:      *
1065:      * @param array $config Associative config array
1066:      *
1067:      * @see config()
1068:      * @static
1069:      * @return Phery
1070:      */
1071:     public static function factory(array $config = array())
1072:     {
1073:         return new Phery($config);
1074:     }
1075: 
1076:     /**
1077:      * Common check for all static factories
1078:      *
1079:      * @param array $attributes
1080:      * @param bool $include_method
1081:      *
1082:      * @return string
1083:      */
1084:     protected static function common_check(&$attributes, $include_method = true)
1085:     {
1086:         if (!empty($attributes['args']))
1087:         {
1088:             $attributes['data-phery-args'] = json_encode($attributes['args']);
1089:             unset($attributes['args']);
1090:         }
1091: 
1092:         if (!empty($attributes['confirm']))
1093:         {
1094:             $attributes['data-phery-confirm'] = $attributes['confirm'];
1095:             unset($attributes['confirm']);
1096:         }
1097: 
1098:         if (!empty($attributes['cache']))
1099:         {
1100:             $attributes['data-phery-cache'] = "1";
1101:             unset($attributes['cache']);
1102:         }
1103: 
1104:         if (!empty($attributes['target']))
1105:         {
1106:             $attributes['data-phery-target'] = $attributes['target'];
1107:             unset($attributes['target']);
1108:         }
1109: 
1110:         if (!empty($attributes['related']))
1111:         {
1112:             $attributes['data-phery-related'] = $attributes['related'];
1113:             unset($attributes['related']);
1114:         }
1115: 
1116:         if (!empty($attributes['phery-type']))
1117:         {
1118:             $attributes['data-phery-type'] = $attributes['phery-type'];
1119:             unset($attributes['phery-type']);
1120:         }
1121: 
1122:         if (!empty($attributes['only']))
1123:         {
1124:             $attributes['data-phery-only'] = $attributes['only'];
1125:             unset($attributes['only']);
1126:         }
1127: 
1128:         if (isset($attributes['clickable']))
1129:         {
1130:             $attributes['data-phery-clickable'] = "1";
1131:             unset($attributes['clickable']);
1132:         }
1133: 
1134:         if ($include_method)
1135:         {
1136:             if (isset($attributes['method']))
1137:             {
1138:                 $attributes['data-phery-method'] = $attributes['method'];
1139:                 unset($attributes['method']);
1140:             }
1141:         }
1142: 
1143:         $encoding = 'UTF-8';
1144:         if (isset($attributes['encoding']))
1145:         {
1146:             $encoding = $attributes['encoding'];
1147:             unset($attributes['encoding']);
1148:         }
1149: 
1150:         return $encoding;
1151:     }
1152: 
1153:     /**
1154:      * Helper function that generates an ajax link, defaults to "A" tag
1155:      *
1156:      * @param string $content    The content of the link. This is ignored for self closing tags, img, input, iframe
1157:      * @param string $function   The PHP function assigned name on Phery::set()
1158:      * @param array  $attributes Extra attributes that can be passed to the link, like class, style, etc
1159:      * <pre>
1160:      * array(
1161:      *     // Display confirmation on click
1162:      *     'confirm' => 'Are you sure?',
1163:      *
1164:      *     // The tag for the item, defaults to a. If the tag is set to img, the
1165:      *     // 'src' must be set in attributes parameter
1166:      *     'tag' => 'a',
1167:      *
1168:      *     // Define another URI for the AJAX call, this defines the HREF of A
1169:      *     'href' => '/path/to/url',
1170:      *
1171:      *     // Extra arguments to pass to the AJAX function, will be stored
1172:      *     // in the data-phery-args attribute as a JSON notation
1173:      *     'args' => array(1, "a"),
1174:      *
1175:      *     // Set the "href" attribute for non-anchor (a) AJAX tags (like buttons or spans).
1176:      *     // Works for A links too, but it won't function without javascript, through data-phery-target
1177:      *     'target' => '/default/ajax/controller',
1178:      *
1179:      *     // Define the data-phery-type for the expected response, json, xml, text, etc
1180:      *     'phery-type' => 'json',
1181:      *
1182:      *     // Enable clicking on structural HTML, like DIV, HEADER, HGROUP, etc
1183:      *     'clickable' => true,
1184:      *
1185:      *     // Force cache of the response
1186:      *     'cache' => true,
1187:      *
1188:      *     // Aggregate data from other DOM elements, can be forms, inputs (textarea, selects),
1189:      *     // pass multiple selectors, like "#input1,#form1,~ input:hidden,select.job"
1190:      *     // that are searched in this order:
1191:      *     // - $(this).find(related)
1192:      *     // - $(related)
1193:      *     // So you can use sibling, children selectors, like ~, +, >, :parent
1194:      *     // You can also, through Javascript, append a jQuery object to the related, using
1195:      *     // $('#element').phery('data', 'related', $('#other_element'));
1196:      *     'related' => true,
1197:      *
1198:      *     // Disables the AJAX on element while the last action is not completed
1199:      *     'only' => true,
1200:      *
1201:      *     // Set the encoding of the data, defaults to UTF-8
1202:      *     'encoding' => 'UTF-8',
1203:      *
1204:      *     // Set the method (for restful responses)
1205:      *     'method' => 'PUT'
1206:      * );
1207:      * </pre>
1208:      *
1209:      * @param Phery  $phery      Pass the current instance of phery, so it can check if the
1210:      *                           functions are defined, and throw exceptions
1211:      * @param boolean $no_close  Don't close the tag, useful if you want to create an AJAX DIV with a lot of content inside,
1212:      *                           but the DIV itself isn't clikable
1213:      *
1214:      * <pre>
1215:      *   <?php echo Phery::link_to('', 'remote', array('target' => '/another-url', 'args' => array('id' => 1), 'class' => 'ajaxified'), null, true); ?>
1216:      *     <p>This new content</p>
1217:      *     <div class="result></div>
1218:      *   </div>
1219:      *   <?php echo Phery::link_to('', 'remote', array('target' => '/another-url', 'args' => array('id' => 2), 'class' => 'ajaxified'), null, true); ?>
1220:      *     <p>Another content will have div result filled</p>
1221:      *     <div class="result></div>
1222:      *   </div>
1223:      *
1224:      *   <script>
1225:      *     $('.ajaxified').phery('remote');
1226:      *   </script>
1227:      * </pre>
1228:      *
1229:      * @static
1230:      * @return string The mounted HTML tag
1231:      */
1232:     public static function link_to($content, $function, array $attributes = array(), Phery $phery = null, $no_close = false)
1233:     {
1234:         if ($phery && !isset($phery->functions[$function]))
1235:         {
1236:             self::exception($phery, 'The function "' . $function . '" provided in "link_to" hasnt been set', self::ERROR_TO);
1237:         }
1238: 
1239:         $tag = 'a';
1240:         if (isset($attributes['tag']))
1241:         {
1242:             $tag = $attributes['tag'];
1243:             unset($attributes['tag']);
1244:         }
1245: 
1246:         $encoding = self::common_check($attributes);
1247: 
1248:         if ($function)
1249:         {
1250:             $attributes['data-phery-remote'] = $function;
1251:         }
1252: 
1253:         $ret = array();
1254:         $ret[] = "<{$tag}";
1255:         foreach ($attributes as $attribute => $value)
1256:         {
1257:             $ret[] = "{$attribute}=\"" . htmlentities($value, ENT_COMPAT, $encoding, false) . "\"";
1258:         }
1259: 
1260:         if (!in_array(strtolower($tag), array('img', 'input', 'iframe', 'hr', 'area', 'embed', 'keygen')))
1261:         {
1262:             $ret[] = ">{$content}";
1263:             if (!$no_close)
1264:             {
1265:                 $ret[] = "</{$tag}>";
1266:             }
1267:         }
1268:         else
1269:         {
1270:             $ret[] = "/>";
1271:         }
1272: 
1273:         return join(' ', $ret);
1274:     }
1275: 
1276:     /**
1277:      * Create a <form> tag with ajax enabled. Must be closed manually with </form>
1278:      *
1279:      * @param string $action   where to go, can be empty
1280:      * @param string $function Registered function name
1281:      * @param array  $attributes Configuration of the element plus any HTML attributes
1282:      *
1283:      * <pre>
1284:      * array(
1285:      *     //Confirmation dialog
1286:      *     'confirm' => 'Are you sure?',
1287:      *
1288:      *     // Type of call, defaults to JSON (to use PheryResponse)
1289:      *     'phery-type' => 'json',
1290:      *
1291:      *     // 'all' submits all elements on the form, even empty ones
1292:      *     // 'disabled' enables submitting disabled elements
1293:      *     'submit' => array('all' => true, 'disabled' => true),
1294:      *
1295:      *     // Disables the AJAX on element while the last action is not completed
1296:      *     'only' => true,
1297:      *
1298:      *     // Set the encoding of the data, defaults to UTF-8
1299:      *     'encoding' => 'UTF-8',
1300:      * );
1301:      * </pre>
1302:      *
1303:      * @param Phery  $phery    Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
1304:      *
1305:      * @static
1306:      * @return string The mounted &lt;form&gt; HTML tag
1307:      */
1308:     public static function form_for($action, $function, array $attributes = array(), Phery $phery = null)
1309:     {
1310:         if (!$function)
1311:         {
1312:             self::exception($phery, 'The "function" argument must be provided to "form_for"', self::ERROR_TO);
1313: 
1314:             return '';
1315:         }
1316: 
1317:         if ($phery && !isset($phery->functions[$function]))
1318:         {
1319:             self::exception($phery, 'The function "' . $function . '" provided in "form_for" hasnt been set', self::ERROR_TO);
1320:         }
1321: 
1322:         $encoding = self::common_check($attributes, false);
1323: 
1324:         if (isset($attributes['submit']))
1325:         {
1326:             $attributes['data-phery-submit'] = json_encode($attributes['submit']);
1327:             unset($attributes['submit']);
1328:         }
1329: 
1330:         $ret = array();
1331:         $ret[] = '<form method="POST" action="' . $action . '" data-phery-remote="' . $function . '"';
1332:         foreach ($attributes as $attribute => $value)
1333:         {
1334:             $ret[] = "{$attribute}=\"" . htmlentities($value, ENT_COMPAT, $encoding, false) . "\"";
1335:         }
1336:         $ret[] = '>';
1337: 
1338:         return join(' ', $ret);
1339:     }
1340: 
1341:     /**
1342:      * Create a <select> element with ajax enabled "onchange" event.
1343:      *
1344:      * @param string $function Registered function name
1345:      * @param array  $items    Options for the select, 'value' => 'text' representation
1346:      * @param array  $attributes Configuration of the element plus any HTML attributes
1347:      *
1348:      * <pre>
1349:      * array(
1350:      *     // Confirmation dialog
1351:      *     'confirm' => 'Are you sure?',
1352:      *
1353:      *     // Type of call, defaults to JSON (to use PheryResponse)
1354:      *     'phery-type' => 'json',
1355:      *
1356:      *     // The URL where it should call, translates to data-phery-target
1357:      *     'target' => '/path/to/php',
1358:      *
1359:      *     // Extra arguments to pass to the AJAX function, will be stored
1360:      *     // in the args attribute as a JSON notation, translates to data-phery-args
1361:      *     'args' => array(1, "a"),
1362:      *
1363:      *     // Set the encoding of the data, defaults to UTF-8
1364:      *     'encoding' => 'UTF-8',
1365:      *
1366:      *     // Disables the AJAX on element while the last action is not completed
1367:      *     'only' => true,
1368:      *
1369:      *     // The current selected value, or array(1,2) for multiple
1370:      *     'selected' => 1
1371:      *
1372:      *     // Set the method (for restful responses)
1373:      *     'method' => 'PUT'
1374:      * );
1375:      * </pre>
1376:      *
1377:      * @param Phery  $phery    Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
1378:      *
1379:      * @static
1380:      * @return string The mounted &lt;select&gt; with &lt;option&gt;s inside
1381:      */
1382:     public static function select_for($function, array $items, array $attributes = array(), Phery $phery = null)
1383:     {
1384:         if ($phery && !isset($phery->functions[$function]))
1385:         {
1386:             self::exception($phery, 'The function "' . $function . '" provided in "select_for" hasnt been set', self::ERROR_TO);
1387:         }
1388: 
1389:         $encoding = self::common_check($attributes);
1390: 
1391:         $selected = array();
1392:         if (isset($attributes['selected']))
1393:         {
1394:             if (is_array($attributes['selected']))
1395:             {
1396:                 // multiple select
1397:                 $selected = $attributes['selected'];
1398:             }
1399:             else
1400:             {
1401:                 // single select
1402:                 $selected = array($attributes['selected']);
1403:             }
1404:             unset($attributes['selected']);
1405:         }
1406: 
1407:         if (isset($attributes['multiple']))
1408:         {
1409:             $attributes['multiple'] = 'multiple';
1410:         }
1411: 
1412:         $ret = array();
1413:         $ret[] = '<select '.($function ? 'data-phery-remote="' . $function . '"' : '');
1414:         foreach ($attributes as $attribute => $value)
1415:         {
1416:             $ret[] = "{$attribute}=\"" . htmlentities($value, ENT_COMPAT, $encoding, false) . "\"";
1417:         }
1418:         $ret[] = '>';
1419: 
1420:         foreach ($items as $value => $text)
1421:         {
1422:             $_value = 'value="' . htmlentities($value, ENT_COMPAT, $encoding, false) . '"';
1423:             if (in_array($value, $selected))
1424:             {
1425:                 $_value .= ' selected="selected"';
1426:             }
1427:             $ret[] = "<option " . ($_value) . ">{$text}</option>\n";
1428:         }
1429:         $ret[] = '</select>';
1430: 
1431:         return join(' ', $ret);
1432:     }
1433: 
1434:     /**
1435:      * OffsetExists
1436:      *
1437:      * @param mixed $offset
1438:      *
1439:      * @return bool
1440:      */
1441:     public function offsetExists($offset)
1442:     {
1443:         return isset($this->data[$offset]);
1444:     }
1445: 
1446:     /**
1447:      * OffsetUnset
1448:      *
1449:      * @param mixed $offset
1450:      */
1451:     public function offsetUnset($offset)
1452:     {
1453:         if (isset($this->data[$offset]))
1454:         {
1455:             unset($this->data[$offset]);
1456:         }
1457:     }
1458: 
1459:     /**
1460:      * OffsetGet
1461:      *
1462:      * @param mixed $offset
1463:      *
1464:      * @return mixed|null
1465:      */
1466:     public function offsetGet($offset)
1467:     {
1468:         if (isset($this->data[$offset]))
1469:         {
1470:             return $this->data[$offset];
1471:         }
1472: 
1473:         return null;
1474:     }
1475: 
1476:     /**
1477:      * offsetSet
1478:      *
1479:      * @param mixed $offset
1480:      * @param mixed $value
1481:      */
1482:     public function offsetSet($offset, $value)
1483:     {
1484:         $this->data[$offset] = $value;
1485:     }
1486: 
1487:     /**
1488:      * Set shared data
1489:      * @param string $name
1490:      * @param mixed $value
1491:      */
1492:     public function __set($name, $value)
1493:     {
1494:         $this->data[$name] = $value;
1495:     }
1496: 
1497:     /**
1498:      * Get shared data
1499:      *
1500:      * @param string $name
1501:      *
1502:      * @return mixed
1503:      */
1504:     public function __get($name)
1505:     {
1506:         if (isset($this->data[$name]))
1507:         {
1508:             return $this->data[$name];
1509:         }
1510: 
1511:         return null;
1512:     }
1513: 
1514:     /**
1515:      * Utility function taken from MYSQL.
1516:      * To not raise any E_NOTICES (if enabled in your error reporting), call it with @ before
1517:      * the variables. Eg.: Phery::coalesce(@$var1, @$var['asdf']);
1518:      *
1519:      * @param mixed $args,... Any number of arguments
1520:      *
1521:      * @return mixed
1522:      */
1523:     public static function coalesce($args)
1524:     {
1525:         $args = func_get_args();
1526:         foreach ($args as &$arg)
1527:         {
1528:             if (isset($arg) && !empty($arg))
1529:             {
1530:                 return $arg;
1531:             }
1532:         }
1533: 
1534:         return null;
1535:     }
1536: }
1537: 
1538: /**
1539:  * Standard response for the json parser
1540:  * @package    Phery
1541:  *
1542:  * @method PheryResponse ajax(string $url, array $settings = null) Perform an asynchronous HTTP (Ajax) request.
1543:  * @method PheryResponse ajaxSetup(array $obj) Set default values for future Ajax requests.
1544:  * @method PheryResponse post(string $url, PheryFunction $success = null) Load data from the server using a HTTP POST request.
1545:  * @method PheryResponse get(string $url, PheryFunction $success = null) Load data from the server using a HTTP GET request.
1546:  * @method PheryResponse getJSON(string $url, PheryFunction $success = null) Load JSON-encoded data from the server using a GET HTTP request.
1547:  * @method PheryResponse getScript(string $url, PheryFunction $success = null) Load a JavaScript file from the server using a GET HTTP request, then execute it.
1548:  * @method PheryResponse detach() Detach a DOM element retaining the events attached to it
1549:  * @method PheryResponse prependTo(string $target) Prepend DOM element to target
1550:  * @method PheryResponse appendTo(string $target) Append DOM element to target
1551:  * @method PheryResponse replaceWith(string $newContent) The content to insert. May be an HTML string, DOM element, or jQuery object.
1552:  * @method PheryResponse css(string $propertyName, mixed $value = null) propertyName: A CSS property name. value: A value to set for the property.
1553:  * @method PheryResponse toggle($duration_or_array_of_options, PheryFunction $complete = null)  Display or hide the matched elements.
1554:  * @method PheryResponse is(string $selector) Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
1555:  * @method PheryResponse hide(string $speed = 0) Hide an object, can be animated with 'fast', 'slow', 'normal'
1556:  * @method PheryResponse show(string $speed = 0) Show an object, can be animated with 'fast', 'slow', 'normal'
1557:  * @method PheryResponse toggleClass(string $className) Add/Remove a class from an element
1558:  * @method PheryResponse data(string $name, mixed $data) Add data to element
1559:  * @method PheryResponse addClass(string $className) Add a class from an element
1560:  * @method PheryResponse removeClass(string $className) Remove a class from an element
1561:  * @method PheryResponse animate(array $prop, int $dur, string $easing = null, PheryFunction $cb = null) Perform a custom animation of a set of CSS properties.
1562:  * @method PheryResponse trigger(string $eventName, array $args = null) Trigger an event
1563:  * @method PheryResponse triggerHandler(string $eventType, array $extraParameters = null) Execute all handlers attached to an element for an event.
1564:  * @method PheryResponse fadeIn(string $speed) Fade in an element
1565:  * @method PheryResponse filter(string $selector) Reduce the set of matched elements to those that match the selector or pass the function's test.
1566:  * @method PheryResponse fadeTo(int $dur, float $opacity) Fade an element to opacity
1567:  * @method PheryResponse fadeOut(string $speed) Fade out an element
1568:  * @method PheryResponse slideUp(int $dur, PheryFunction $cb = null) Hide with slide up animation
1569:  * @method PheryResponse slideDown(int $dur, PheryFunction $cb = null) Show with slide down animation
1570:  * @method PheryResponse slideToggle(int $dur, PheryFunction $cb = null) Toggle show/hide the element, using slide animation
1571:  * @method PheryResponse unbind(string $name) Unbind an event from an element
1572:  * @method PheryResponse undelegate() Remove a handler from the event for all elements which match the current selector, now or in the future, based upon a specific set of root elements.
1573:  * @method PheryResponse stop() Stop animation on elements
1574:  * @method PheryResponse val(string $content) Set the value of an element
1575:  * @method PheryResponse removeData(string $name) Remove element data added with data()
1576:  * @method PheryResponse removeAttr(string $name) Remove an attribute from an element
1577:  * @method PheryResponse scrollTop(int $val) Set the scroll from the top
1578:  * @method PheryResponse scrollLeft(int $val) Set the scroll from the left
1579:  * @method PheryResponse height(int $val = null) Get or set the height from the left
1580:  * @method PheryResponse width(int $val = null) Get or set the width from the left
1581:  * @method PheryResponse slice(int $start, int $end) Reduce the set of matched elements to a subset specified by a range of indices.
1582:  * @method PheryResponse not(string $val) Remove elements from the set of matched elements.
1583:  * @method PheryResponse eq(int $selector) Reduce the set of matched elements to the one at the specified index.
1584:  * @method PheryResponse offset(array $coordinates) Set the current coordinates of every element in the set of matched elements, relative to the document.
1585:  * @method PheryResponse map(PheryFunction $callback) Pass each element in the current matched set through a function, producing a new jQuery object containing the return values.
1586:  * @method PheryResponse children(string $selector) Get the children of each element in the set of matched elements, optionally filtered by a selector.
1587:  * @method PheryResponse closest(string $selector) Get the first ancestor element that matches the selector, beginning at the current element and progressing up through the DOM tree.
1588:  * @method PheryResponse find(string $selector) Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
1589:  * @method PheryResponse next(string $selector = null) Get the immediately following sibling of each element in the set of matched elements, optionally filtered by a selector.
1590:  * @method PheryResponse nextAll(string $selector) Get all following siblings of each element in the set of matched elements, optionally filtered by a selector.
1591:  * @method PheryResponse nextUntil(string $selector) Get all following siblings of each element up to  but not including the element matched by the selector.
1592:  * @method PheryResponse parentsUntil(string $selector) Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector.
1593:  * @method PheryResponse offsetParent() Get the closest ancestor element that is positioned.
1594:  * @method PheryResponse parent(string $selector = null) Get the parent of each element in the current set of matched elements, optionally filtered by a selector.
1595:  * @method PheryResponse parents(string $selector) Get the ancestors of each element in the current set of matched elements, optionally filtered by a selector.
1596:  * @method PheryResponse prev(string $selector = null) Get the immediately preceding sibling of each element in the set of matched elements, optionally filtered by a selector.
1597:  * @method PheryResponse prevAll(string $selector) Get all preceding siblings of each element in the set of matched elements, optionally filtered by a selector.
1598:  * @method PheryResponse prevUntil(string $selector) Get the ancestors of each element in the current set of matched elements, optionally filtered by a selector.
1599:  * @method PheryResponse siblings(string $selector) Get the siblings of each element in the set of matched elements, optionally filtered by a selector.
1600:  * @method PheryResponse add(PheryResponse $selector) Add elements to the set of matched elements.
1601:  * @method PheryResponse contents() Get the children of each element in the set of matched elements, including text nodes.
1602:  * @method PheryResponse end() End the most recent filtering operation in the current chain and return the set of matched elements to its previous state.
1603:  * @method PheryResponse after(string $content) Insert content, specified by the parameter, after each element in the set of matched elements.
1604:  * @method PheryResponse before(string $content) Insert content, specified by the parameter, before each element in the set of matched elements.
1605:  * @method PheryResponse insertAfter(string $target) Insert every element in the set of matched elements after the target.
1606:  * @method PheryResponse insertBefore(string $target) Insert every element in the set of matched elements before the target.
1607:  * @method PheryResponse unwrap() Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
1608:  * @method PheryResponse wrap(string $wrappingElement) Wrap an HTML structure around each element in the set of matched elements.
1609:  * @method PheryResponse wrapAll(string $wrappingElement) Wrap an HTML structure around all elements in the set of matched elements.
1610:  * @method PheryResponse wrapInner(string $wrappingElement) Wrap an HTML structure around the content of each element in the set of matched elements.
1611:  * @method PheryResponse delegate(string $selector, string $eventType, PheryFunction $handler) Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.
1612:  * @method PheryResponse one(string $eventType, PheryFunction $handler) Attach a handler to an event for the elements. The handler is executed at most once per element.
1613:  * @method PheryResponse bind(string $eventType, PheryFunction $handler) Attach a handler to an event for the elements.
1614:  * @method PheryResponse each(PheryFunction $function) Iterate over a jQ object, executing a function for each matched element.
1615:  * @method PheryResponse phery(string $function = null, array $args = null) Access the phery() on the select element(s)
1616:  * @method PheryResponse addBack(string $selector = null) Add the previous set of elements on the stack to the current set, optionally filtered by a selector.
1617:  * @method PheryResponse clearQueue(string $queueName = null) Remove from the queue all items that have not yet been run.
1618:  * @method PheryResponse clone(boolean $withDataAndEvents = null, boolean $deepWithDataAndEvents = null) Create a deep copy of the set of matched elements.
1619:  * @method PheryResponse dblclick(array $eventData = null, PheryFunction $handler = null) Bind an event handler to the "dblclick" JavaScript event, or trigger that event on an element.
1620:  * @method PheryResponse always(PheryFunction $callback) Bind an event handler to the "dblclick" JavaScript event, or trigger that event on an element.
1621:  * @method PheryResponse done(PheryFunction $callback) Add handlers to be called when the Deferred object is resolved.
1622:  * @method PheryResponse fail(PheryFunction $callback) Add handlers to be called when the Deferred object is rejected.
1623:  * @method PheryResponse progress(PheryFunction $callback) Add handlers to be called when the Deferred object is either resolved or rejected.
1624:  * @method PheryResponse then(PheryFunction $donecallback, PheryFunction $failcallback = null, PheryFunction $progresscallback = null) Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
1625:  * @method PheryResponse empty() Remove all child nodes of the set of matched elements from the DOM.
1626:  * @method PheryResponse finish(string $queue) Stop the currently-running animation, remove all queued animations, and complete all animations for the matched elements.
1627:  * @method PheryResponse focus(array $eventData = null, PheryFunction $handler = null)  Bind an event handler to the "focusout" JavaScript event.
1628:  * @method PheryResponse focusin(array $eventData = null, PheryFunction $handler = null)  Bind an event handler to the "focusin" event.
1629:  * @method PheryResponse focusout(array $eventData = null, PheryFunction $handler = null) Bind an event handler to the "focus" JavaScript event, or trigger that event on an element.
1630:  * @method PheryResponse has(string $selector) Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element.
1631:  * @method PheryResponse index(string $selector = null) Search for a given element from among the matched elements.
1632:  * @method PheryResponse on(string $events, string $selector, array $data = null, PheryFunction $handler = null) Attach an event handler function for one or more events to the selected elements.
1633:  * @method PheryResponse off(string $events, string $selector = null, PheryFunction $handler = null) Remove an event handler.
1634:  * @method PheryResponse prop(string $propertyName, $data_or_function = null) Set one or more properties for the set of matched elements.
1635:  * @method PheryResponse promise(string $type = null, array $target = null) Return a Promise object to observe when all actions of a certain type bound to the collection, queued or not, have finished.
1636:  * @method PheryResponse pushStack(array $elements, string $name = null, array $arguments = null) Add a collection of DOM elements onto the jQuery stack.
1637:  * @method PheryResponse removeProp(string $propertyName) Remove a property for the set of matched elements.
1638:  * @method PheryResponse resize($eventData_or_function = null, PheryFunction $handler = null) Bind an event handler to the "resize" JavaScript event, or trigger that event on an element.
1639:  * @method PheryResponse scroll($eventData_or_function = null, PheryFunction $handler = null) Bind an event handler to the "scroll" JavaScript event, or trigger that event on an element.
1640:  * @method PheryResponse select($eventData_or_function = null, PheryFunction $handler = null) Bind an event handler to the "select" JavaScript event, or trigger that event on an element.
1641:  * @method PheryResponse serializeArray() Encode a set of form elements as an array of names and values.
1642:  * @method PheryResponse replaceAll(string $target) Replace each target element with the set of matched elements.
1643:  * @method PheryResponse reset() Reset a form element.
1644:  * @method PheryResponse toArray() Retrieve all the DOM elements contained in the jQuery set, as an array.
1645:  * @property PheryResponse this The DOM element that is making the AJAX call
1646:  * @property PheryResponse jquery The $ jQuery object, can be used to call $.getJSON, $.getScript, etc
1647:  * @property PheryResponse window Shortcut for jquery('window') / $(window)
1648:  * @property PheryResponse document Shortcut for jquery('document') / $(document)
1649:  */
1650: class PheryResponse extends ArrayObject {
1651: 
1652:     /**
1653:      * All responses that were created in the run, access them through their name
1654:      * @var PheryResponse[]
1655:      */
1656:     protected static $responses = array();
1657:     /**
1658:      * Common data available to all responses
1659:      * @var array
1660:      */
1661:     protected static $global = array();
1662:     /**
1663:      * Last jQuery selector defined
1664:      * @var string
1665:      */
1666:     protected $last_selector = null;
1667:     /**
1668:      * Restore the selector if set
1669:      * @var string
1670:      */
1671:     protected $restore = null;
1672:     /**
1673:      * Array containing answer data
1674:      * @var array
1675:      */
1676:     protected $data = array();
1677:     /**
1678:      * Array containing merged data
1679:      * @var array
1680:      */
1681:     protected $merged = array();
1682:     /**
1683:      * This response config
1684:      * @var array
1685:      */
1686:     protected $config = array();
1687:     /**
1688:      * Name of the current response
1689:      * @var string
1690:      */
1691:     protected $name = null;
1692:     /**
1693:      * Internal count for multiple paths
1694:      * @var int
1695:      */
1696:     protected static $internal_count = 0;
1697:     /**
1698:      * Internal count for multiple commands
1699:      * @var int
1700:      */
1701:     protected $internal_cmd_count = 0;
1702:     /**
1703:      * Is the criteria from unless fulfilled?
1704:      * @var bool
1705:      */
1706:     protected $matched = true;
1707: 
1708:     /**
1709:      * Construct a new response
1710:      *
1711:      * @param string $selector Create the object already selecting the DOM element
1712:      * @param array $constructor Only available if you are creating an element, like $('&lt;p/&gt;')
1713:      */
1714:     public function __construct($selector = null, array $constructor = array())
1715:     {
1716:         parent::__construct();
1717: 
1718:         $this->config = array(
1719:             'typecast_objects' => true,
1720:             'convert_integers' => true,
1721:         );
1722: 
1723:         $this->jquery($selector, $constructor);
1724: 
1725:         $this->set_response_name(uniqid("", true));
1726:     }
1727: 
1728:     /**
1729:      * Change the config for this response
1730:      * You may pass in an associative array of your config
1731:      *
1732:      * @param array $config
1733:      * <pre>
1734:      * array(
1735:      *   'convert_integers' => true/false
1736:      *   'typecast_objects' => true/false
1737:      * </pre>
1738:      *
1739:      * @return PheryResponse
1740:      */
1741:     public function set_config(array $config)
1742:     {
1743:         if (isset($config['convert_integers']))
1744:         {
1745:             $this->config['convert_integers'] = (bool)$config['convert_integers'];
1746:         }
1747: 
1748:         if (isset($config['typecast_objects']))
1749:         {
1750:             $this->config['typecast_objects'] = (bool)$config['typecast_objects'];
1751:         }
1752: 
1753:         return $this;
1754:     }
1755: 
1756:     /**
1757:      * Increment the internal counter, so there are no conflicting stacked commands
1758:      *
1759:      * @param string $type Selector
1760:      * @param boolean $force Force unajusted selector into place
1761:      * @return string The previous overwritten selector
1762:      */
1763:     protected function set_internal_counter($type, $force = false)
1764:     {
1765:         $last = $this->last_selector;
1766:         if ($force && $last !== null && !isset($this->data[$last])) {
1767:             $this->data[$last] = array();
1768:         }
1769:         $this->last_selector = '{'.$type.(self::$internal_count++).'}';
1770:         return $last;
1771:     }
1772: 
1773:     /**
1774:      * Renew the CSRF token on a given Phery instance
1775:      * Resets any selectors that were being chained before
1776:      *
1777:      * @param Phery $instance Instance of Phery
1778:      * @return PheryResponse
1779:      */
1780:     public function renew_csrf(Phery $instance)
1781:     {
1782:         if ($instance->config('csrf') === true)
1783:         {
1784:             $this->cmd(13, array($instance->csrf()));
1785:         }
1786: 
1787:         return $this;
1788:     }
1789: 
1790:     /**
1791:      * Set the name of this response
1792:      *
1793:      * @param string $name Name of current response
1794:      *
1795:      * @return PheryResponse
1796:      */
1797:     public function set_response_name($name)
1798:     {
1799:         if (!empty($this->name))
1800:         {
1801:             unset(self::$responses[$this->name]);
1802:         }
1803:         $this->name = $name;
1804:         self::$responses[$this->name] = $this;
1805: 
1806:         return $this;
1807:     }
1808: 
1809:     /**
1810:      * Broadcast a remote message to the client to all elements that
1811:      * are subscribed to them. This removes the current selector if any
1812:      *
1813:      * @param string $name Name of the browser subscribed topic on the element
1814:      * @param array [$params] Any params to pass to the subscribed topic
1815:      *
1816:      * @return PheryResponse
1817:      */
1818:     public function phery_broadcast($name, array $params = array())
1819:     {
1820:         $this->last_selector = null;
1821:         return $this->cmd(12, array($name, array($this->typecast($params, true, true)), true));
1822:     }
1823: 
1824:     /**
1825:      * Publish a remote message to the client that is subscribed to them
1826:      * This removes the current selector (if any)
1827:      *
1828:      * @param string $name Name of the browser subscribed topic on the element
1829:      * @param array [$params] Any params to pass to the subscribed topic
1830:      *
1831:      * @return PheryResponse
1832:      */
1833:     public function publish($name, array $params = array())
1834:     {
1835:         $this->last_selector = null;
1836:         return $this->cmd(12, array($name, array($this->typecast($params, true, true))));
1837:     }
1838: 
1839:     /**
1840:      * Get the name of this response
1841:      *
1842:      * @return null|string
1843:      */
1844:     public function get_response_name()
1845:     {
1846:         return $this->name;
1847:     }
1848: 
1849:     /**
1850:      * Borrowed from Ruby, the next imediate instruction will be executed unless
1851:      * it matches this criteria.
1852:      *
1853:      * <code>
1854:      *   $count = 3;
1855:      *   PheryResponse::factory()
1856:      *     // if not $count equals 2 then
1857:      *     ->unless($count === 2)
1858:      *     ->call('func'); // This won't trigger, $count is 2
1859:      * </code>
1860:      *
1861:      * <code>
1862:      *   PheryResponse::factory('.widget')
1863:      *     ->unless(PheryFunction::factory('return !this.hasClass("active");'), true)
1864:      *     ->remove(); // This won't remove if the element have the active class
1865:      * </code>
1866:      *
1867:      *
1868:      * @param boolean|PheryFunction $condition
1869:      * When not remote, can be any criteria that evaluates to FALSE.
1870:      * When it's remote, if passed a PheryFunction, it will skip the next
1871:      * iteration unless the return value of the PheryFunction is false.
1872:      * Passing a PheryFunction automatically sets $remote param to true
1873:      *
1874:      * @param bool $remote
1875:      * Instead of doing it in the server side, do it client side, for example,
1876:      * append something ONLY if an element exists. The context (this) of the function
1877:      * will be the last selected element or the calling element.
1878:      *
1879:      * @return PheryResponse
1880:      */
1881:     public function unless($condition, $remote = false)
1882:     {
1883:         if (!$remote && !($condition instanceof PheryFunction) && !($condition instanceof PheryResponse))
1884:         {
1885:             $this->matched = !$condition;
1886:         }
1887:         else
1888:         {
1889:             $this->set_internal_counter('!', true);
1890:             $this->cmd(0xff, array($this->typecast($condition, true, true)));
1891:         }
1892: 
1893:         return $this;
1894:     }
1895: 
1896:     /**
1897:      * It's the opposite of unless(), the next command will be issued in
1898:      * case the condition is true
1899:      *
1900:      * <code>
1901:      *   $count = 3;
1902:      *   PheryResponse::factory()
1903:      *     // if $count is greater than 2 then
1904:      *     ->incase($count > 2)
1905:      *     ->call('func'); // This will be executed, $count is greater than 2
1906:      * </code>
1907:      *
1908:      * <code>
1909:      *   PheryResponse::factory('.widget')
1910:      *     ->incase(PheryFunction::factory('return this.hasClass("active");'), true)
1911:      *     ->remove(); // This will remove the element if it has the active class
1912:      * </code>
1913:      *
1914:      * @param boolean|callable|PheryFunction $condition
1915:      * When not remote, can be any criteria that evaluates to TRUE.
1916:      * When it's remote, if passed a PheryFunction, it will execute the next
1917:      * iteration when the return value of the PheryFunction is true
1918:      *
1919:      * @param bool $remote
1920:      * Instead of doing it in the server side, do it client side, for example,
1921:      * append something ONLY if an element exists. The context (this) of the function
1922:      * will be the last selected element or the calling element.
1923:      *
1924:      * @return PheryResponse
1925:      */
1926:     public function incase($condition, $remote = false)
1927:     {
1928:         if (!$remote && !($condition instanceof PheryFunction) && !($condition instanceof PheryResponse))
1929:         {
1930:             $this->matched = $condition;
1931:         }
1932:         else
1933:         {
1934:             $this->set_internal_counter('=', true);
1935:             $this->cmd(0xff, array($this->typecast($condition, true, true)));
1936:         }
1937: 
1938:         return $this;
1939:     }
1940: 
1941:     /**
1942:      * This helper function is intended to normalize the $_FILES array, because when uploading multiple
1943:      * files, the order gets messed up. The result will always be in the format:
1944:      *
1945:      * <code>
1946:      * array(
1947:      *    'name of the file input' => array(
1948:      *       array(
1949:      *         'name' => ...,
1950:      *         'tmp_name' => ...,
1951:      *         'type' => ...,
1952:      *         'error' => ...,
1953:      *         'size' => ...,
1954:      *       ),
1955:      *       array(
1956:      *         'name' => ...,
1957:      *         'tmp_name' => ...,
1958:      *         'type' => ...,
1959:      *         'error' => ...,
1960:      *         'size' => ...,
1961:      *       ),
1962:      *    )
1963:      * );
1964:      * </code>
1965:      *
1966:      * So you can always do like (regardless of one or multiple files uploads)
1967:      *
1968:      * <code>
1969:      * <input name="avatar" type="file" multiple>
1970:      * <input name="pic" type="file">
1971:      *
1972:      * <?php
1973:      * foreach(PheryResponse::files('avatar') as $index => $file){
1974:      *     if (is_uploaded_file($file['tmp_name'])){
1975:      *        //...
1976:      *     }
1977:      * }
1978:      *
1979:      * foreach(PheryResponse::files() as $field => $group){
1980:      *   foreach ($group as $file){
1981:      *     if (is_uploaded_file($file['tmp_name'])){
1982:      *       if ($field === 'avatar') {
1983:      *          //...
1984:      *       } else if ($field === 'pic') {
1985:      *          //...
1986:      *       }
1987:      *     }
1988:      *   }
1989:      * }
1990:      * ?>
1991:      * </code>
1992:      *
1993:      * If no files were uploaded, returns an empty array.
1994:      *
1995:      * @param string|bool $group Pluck out the file group directly
1996:      * @return array
1997:      */
1998:     public static function files($group = false)
1999:     {
2000:         $result = array();
2001: 
2002:         foreach ($_FILES as $name => $keys)
2003:         {
2004:             if (is_array($keys))
2005:             {
2006:                 if (is_array($keys['name']))
2007:                 {
2008:                     $len = count($keys['name']);
2009:                     for ($i = 0; $i < $len; $i++)
2010:                     {
2011:                         $result[$name][$i] = array(
2012:                             'name' => $keys['name'][$i],
2013:                             'tmp_name' => $keys['tmp_name'][$i],
2014:                             'type' => $keys['type'][$i],
2015:                             'error' => $keys['error'][$i],
2016:                             'size' => $keys['size'][$i],
2017:                         );
2018:                     }
2019:                 }
2020:                 else
2021:                 {
2022:                     $result[$name] = array(
2023:                         $keys
2024:                     );
2025:                 }
2026:             }
2027:         }
2028: 
2029:         return $group !== false && isset($result[$group]) ? $result[$group] : $result;
2030:     }
2031: 
2032:     /**
2033:      * Set a global value that can be accessed through $pheryresponse['value']
2034:      * It's available in all responses, and can also be acessed using self['value']
2035:      *
2036:      * @param array|string Key => value combination or the name of the global
2037:      * @param mixed $value [Optional]
2038:      */
2039:     public static function set_global($name, $value = null)
2040:     {
2041:         if (isset($name) && is_array($name))
2042:         {
2043:             foreach ($name as $n => $v)
2044:             {
2045:                 self::$global[$n] = $v;
2046:             }
2047:         }
2048:         else
2049:         {
2050:             self::$global[$name] = $value;
2051:         }
2052:     }
2053: 
2054:     /**
2055:      * Unset a global variable
2056:      *
2057:      * @param string $name Variable name
2058:      */
2059:     public static function unset_global($name)
2060:     {
2061:         unset(self::$global[$name]);
2062:     }
2063: 
2064:     /**
2065:      * Will check for globals and local values
2066:      *
2067:      * @param string|int $index
2068:      *
2069:      * @return mixed
2070:      */
2071:     public function offsetExists($index)
2072:     {
2073:         if (isset(self::$global[$index]))
2074:         {
2075:             return true;
2076:         }
2077: 
2078:         return parent::offsetExists($index);
2079:     }
2080: 
2081:     /**
2082:      * Set local variables, will be available only in this instance
2083:      *
2084:      * @param string|int|null $index
2085:      * @param mixed           $newval
2086:      *
2087:      * @return void
2088:      */
2089:     public function offsetSet($index, $newval)
2090:     {
2091:         if ($index === null)
2092:         {
2093:             $this[] = $newval;
2094:         }
2095:         else
2096:         {
2097:             parent::offsetSet($index, $newval);
2098:         }
2099:     }
2100: 
2101:     /**
2102:      * Return null if no value
2103:      *
2104:      * @param mixed $index
2105:      *
2106:      * @return mixed|null
2107:      */
2108:     public function offsetGet($index)
2109:     {
2110:         if (parent::offsetExists($index))
2111:         {
2112:             return parent::offsetGet($index);
2113:         }
2114:         if (isset(self::$global[$index]))
2115:         {
2116:             return self::$global[$index];
2117:         }
2118: 
2119:         return null;
2120:     }
2121: 
2122:     /**
2123:      * Get a response by name
2124:      *
2125:      * @param string $name
2126:      *
2127:      * @return PheryResponse|null
2128:      */
2129:     public static function get_response($name)
2130:     {
2131:         if (isset(self::$responses[$name]) && self::$responses[$name] instanceof PheryResponse)
2132:         {
2133:             return self::$responses[$name];
2134:         }
2135: 
2136:         return null;
2137:     }
2138: 
2139:     /**
2140:      * Get merged response data as a new PheryResponse.
2141:      * This method works like a constructor if the previous response was destroyed
2142:      *
2143:      * @param string $name Name of the merged response
2144:      * @return PheryResponse|null
2145:      */
2146:     public function get_merged($name)
2147:     {
2148:         if (isset($this->merged[$name]))
2149:         {
2150:             if (isset(self::$responses[$name]))
2151:             {
2152:                 return self::$responses[$name];
2153:             }
2154:             $response = new PheryResponse;
2155:             $response->data = $this->merged[$name];
2156:             return $response;
2157:         }
2158:         return null;
2159:     }
2160: 
2161:     /**
2162:      * Same as phery.remote()
2163:      *
2164:      * @param string  $remote     Function
2165:      * @param array   $args       Arguments to pass to the
2166:      * @param array   $attr       Here you may set like method, target, type, cache, proxy
2167:      * @param boolean $directCall Setting to false returns the jQuery object, that can bind
2168:      *                            events, append to DOM, etc
2169:      *
2170:      * @return PheryResponse
2171:      */
2172:     public function phery_remote($remote, $args = array(), $attr = array(), $directCall = true)
2173:     {
2174:         $this->set_internal_counter('-');
2175: 
2176:         return $this->cmd(0xff, array(
2177:             $remote,
2178:             $args,
2179:             $attr,
2180:             $directCall
2181:         ));
2182:     }
2183: 
2184:     /**
2185:      * Set a global variable, that can be accessed directly through window object,
2186:      * can set properties inside objects if you pass an array as the variable.
2187:      * If it doesn't exist it will be created
2188:      *
2189:      * <code>
2190:      * // window.customer_info = {'name': 'John','surname': 'Doe', 'age': 39}
2191:      * PheryResponse::factory()->set_var('customer_info', array('name' => 'John', 'surname' => 'Doe', 'age' => 39));
2192:      * </code>
2193:      *
2194:      * <code>
2195:      * // window.customer_info.name = 'John'
2196:      * PheryResponse::factory()->set_var(array('customer_info','name'), 'John');
2197:      * </code>
2198:      *
2199:      * @param string|array $variable Global variable name
2200:      * @param mixed        $data     Any data
2201:      * @return PheryResponse
2202:      */
2203:     public function set_var($variable, $data)
2204:     {
2205:         $this->last_selector = null;
2206: 
2207:         if (!empty($data) && is_array($data))
2208:         {
2209:             foreach ($data as $name => $d)
2210:             {
2211:                 $data[$name] = $this->typecast($d, true, true);
2212:             }
2213:         }
2214:         else
2215:         {
2216:             $data = $this->typecast($data, true, true);
2217:         }
2218: 
2219:         return $this->cmd(9, array(
2220:             !is_array($variable) ? array($variable) : $variable,
2221:             array($data)
2222:         ));
2223:     }
2224: 
2225:     /**
2226:      * Delete a global variable, that can be accessed directly through window, can unset object properties,
2227:      * if you pass an array
2228:      *
2229:      * <code>
2230:      * PheryResponse::factory()->unset('customer_info');
2231:      * </code>
2232:      *
2233:      * <code>
2234:      * PheryResponse::factory()->unset(array('customer_info','name')); // translates to delete customer_info['name']
2235:      * </code>
2236:      *
2237:      * @param string|array $variable Global variable name
2238:      * @return PheryResponse
2239:      */
2240:     public function unset_var($variable)
2241:     {
2242:         $this->last_selector = null;
2243: 
2244:         return $this->cmd(9, array(
2245:             !is_array($variable) ? array($variable) : $variable,
2246:         ));
2247:     }
2248: 
2249:     /**
2250:      * Create a new PheryResponse instance for chaining, fast and effective for one line returns
2251:      *
2252:      * <code>
2253:      * function answer($data)
2254:      * {
2255:      *  return
2256:      *         PheryResponse::factory('a#link-'.$data['rel'])
2257:      *         ->attr('href', '#')
2258:      *         ->alert('done');
2259:      * }
2260:      * </code>
2261:      *
2262:      * @param string $selector optional
2263:      * @param array $constructor Same as $('&lt;p/&gt;', {})
2264:      *
2265:      * @static
2266:      * @return PheryResponse
2267:      */
2268:     public static function factory($selector = null, array $constructor = array())
2269:     {
2270:         return new PheryResponse($selector, $constructor);
2271:     }
2272: 
2273:     /**
2274:      * Remove a batch of calls for a selector. Won't remove for merged responses.
2275:      * Passing an integer, will remove commands, like dump_vars, call, etc, in the
2276:      * order they were called
2277:      *
2278:      * @param string|int $selector
2279:      *
2280:      * @return PheryResponse
2281:      */
2282:     public function remove_selector($selector)
2283:     {
2284:         if ((is_string($selector) || is_int($selector)) && isset($this->data[$selector]))
2285:         {
2286:             unset($this->data[$selector]);
2287:         }
2288: 
2289:         return $this;
2290:     }
2291: 
2292:     /**
2293:      * Access the current calling DOM element without the need for IDs, names, etc
2294:      * Use $response->this (as a property) instead
2295:      *
2296:      * @deprecated
2297:      * @return PheryResponse
2298:      */
2299:     public function this()
2300:     {
2301:         return $this->this;
2302:     }
2303: 
2304:     /**
2305:      * Merge another response to this one.
2306:      * Selectors with the same name will be added in order, for example:
2307:      *
2308:      * <code>
2309:      * function process()
2310:      * {
2311:      *      $response = PheryResponse::factory('a.links')->remove();
2312:      *      // $response will execute before
2313:      *      // there will be no more "a.links" in the DOM, so the addClass() will fail silently
2314:      *      // to invert the order, merge $response to $response2
2315:      *      $response2 = PheryResponse::factory('a.links')->addClass('red');
2316:      *      return $response->merge($response2);
2317:      * }
2318:      * </code>
2319:      *
2320:      * @param PheryResponse|string $phery_response Another PheryResponse object or a name of response
2321:      *
2322:      * @return PheryResponse
2323:      */
2324:     public function merge($phery_response)
2325:     {
2326:         if (is_string($phery_response))
2327:         {
2328:             if (isset(self::$responses[$phery_response]))
2329:             {
2330:                 $this->merged[self::$responses[$phery_response]->name] = self::$responses[$phery_response]->data;
2331:             }
2332:         }
2333:         elseif ($phery_response instanceof PheryResponse)
2334:         {
2335:             $this->merged[$phery_response->name] = $phery_response->data;
2336:         }
2337: 
2338:         return $this;
2339:     }
2340: 
2341:     /**
2342:      * Remove a previously merged response, if you pass TRUE will removed all merged responses
2343:      *
2344:      * @param PheryResponse|string|boolean $phery_response
2345:      *
2346:      * @return PheryResponse
2347:      */
2348:     public function unmerge($phery_response)
2349:     {
2350:         if (is_string($phery_response))
2351:         {
2352:             if (isset(self::$responses[$phery_response]))
2353:             {
2354:                 unset($this->merged[self::$responses[$phery_response]->name]);
2355:             }
2356:         }
2357:         elseif ($phery_response instanceof PheryResponse)
2358:         {
2359:             unset($this->merged[$phery_response->name]);
2360:         }
2361:         elseif ($phery_response === true)
2362:         {
2363:             $this->merged = array();
2364:         }
2365: 
2366:         return $this;
2367:     }
2368: 
2369:     /**
2370:      * Pretty print to console.log
2371:      *
2372:      * @param mixed $vars,... Any var
2373:      *
2374:      * @return PheryResponse
2375:      */
2376:     public function print_vars($vars)
2377:     {
2378:         $this->last_selector = null;
2379: 
2380:         $args = array();
2381:         foreach (func_get_args() as $name => $arg)
2382:         {
2383:             if (is_object($arg))
2384:             {
2385:                 $arg = get_object_vars($arg);
2386:             }
2387:             $args[$name] = array(var_export($arg, true));
2388:         }
2389: 
2390:         return $this->cmd(6, $args);
2391:     }
2392: 
2393:     /**
2394:      * Dump var to console.log
2395:      *
2396:      * @param mixed $vars,... Any var
2397:      *
2398:      * @return PheryResponse
2399:      */
2400:     public function dump_vars($vars)
2401:     {
2402:         $this->last_selector = null;
2403:         $args = array();
2404:         foreach (func_get_args() as $index => $func)
2405:         {
2406:             if ($func instanceof PheryResponse || $func instanceof PheryFunction)
2407:             {
2408:                 $args[$index] = array($this->typecast($func, true, true));
2409:             }
2410:             elseif (is_object($func))
2411:             {
2412:                 $args[$index] = array(get_object_vars($func));
2413:             }
2414:             else
2415:             {
2416:                 $args[$index] = array($func);
2417:             }
2418:         }
2419: 
2420:         return $this->cmd(6, $args);
2421:     }
2422: 
2423:     /**
2424:      * Sets the jQuery selector, so you can chain many calls to it.
2425:      *
2426:      * <code>
2427:      * PheryResponse::factory()
2428:      * ->jquery('.slides')
2429:      * ->fadeTo(0,0)
2430:      * ->css(array('top' => '10px', 'left' => '90px'));
2431:      * </code>
2432:      *
2433:      * For creating an element
2434:      *
2435:      * <code>
2436:      * PheryResponse::factory()
2437:      * ->jquery('.slides', array(
2438:      *   'css' => array(
2439:      *     'left': '50%',
2440:      *     'textDecoration': 'underline'
2441:      *   )
2442:      * ))
2443:      * ->appendTo('body');
2444:      * </code>
2445:      *
2446:      * @param string $selector Sets the current selector for subsequent chaining, like you would using $()
2447:      * @param array $constructor Only available if you are creating a new element, like $('&lt;p/&gt;', {'class': 'classname'})
2448:      *
2449:      * @return PheryResponse
2450:      */
2451:     public function jquery($selector, array $constructor = array())
2452:     {
2453:         if ($selector)
2454:         {
2455:             $this->last_selector = $selector;
2456:         }
2457: 
2458:         if (isset($selector) && is_string($selector) && count($constructor) && substr($selector, 0, 1) === '<')
2459:         {
2460:             foreach ($constructor as $name => $value)
2461:             {
2462:                 $this->$name($value);
2463:             }
2464:         }
2465:         return $this;
2466:     }
2467: 
2468:     /**
2469:      * Shortcut/alias for jquery($selector) Passing null works like jQuery.func
2470:      *
2471:      * @param string $selector Sets the current selector for subsequent chaining
2472:      * @param array $constructor Only available if you are creating a new element, like $('&lt;p/&gt;', {})
2473:      *
2474:      * @return PheryResponse
2475:      */
2476:     public function j($selector, array $constructor = array())
2477:     {
2478:         return $this->jquery($selector, $constructor);
2479:     }
2480: 
2481:     /**
2482:      * Show an alert box
2483:      *
2484:      * @param string $msg Message to be displayed
2485:      *
2486:      * @return PheryResponse
2487:      */
2488:     public function alert($msg)
2489:     {
2490:         if (is_array($msg))
2491:         {
2492:             $msg = join("\n", $msg);
2493:         }
2494: 
2495:         $this->last_selector = null;
2496: 
2497:         return $this->cmd(1, array($this->typecast($msg, true)));
2498:     }
2499: 
2500:     /**
2501:      * Pass JSON to the browser
2502:      *
2503:      * @param mixed $obj Data to be encoded to json (usually an array or a JsonSerializable)
2504:      *
2505:      * @return PheryResponse
2506:      */
2507:     public function json($obj)
2508:     {
2509:         $this->last_selector = null;
2510: 
2511:         return $this->cmd(4, array(json_encode($obj)));
2512:     }
2513: 
2514:     /**
2515:      * Remove the current jQuery selector
2516:      *
2517:      * @param string|boolean $selector Set a selector
2518:      *
2519:      * @return PheryResponse
2520:      */
2521:     public function remove($selector = null)
2522:     {
2523:         return $this->cmd('remove', array(), $selector);
2524:     }
2525: 
2526:     /**
2527:      * Add a command to the response
2528:      *
2529:      * @param int|string|array $cmd      Integer for command, see Phery.js for more info
2530:      * @param array            $args     Array to pass to the response
2531:      * @param string           $selector Insert the jquery selector
2532:      *
2533:      * @return PheryResponse
2534:      */
2535:     public function cmd($cmd, array $args = array(), $selector = null)
2536:     {
2537:         if (!$this->matched)
2538:         {
2539:             $this->matched = true;
2540:             return $this;
2541:         }
2542: 
2543:         $selector = Phery::coalesce($selector, $this->last_selector);
2544: 
2545:         if ($selector === null)
2546:         {
2547:             $this->data['0'.($this->internal_cmd_count++)] = array(
2548:                 'c' => $cmd,
2549:                 'a' => $args
2550:             );
2551:         }
2552:         else
2553:         {
2554:             if (!isset($this->data[$selector]))
2555:             {
2556:                 $this->data[$selector] = array();
2557:             }
2558:             $this->data[$selector][] = array(
2559:                 'c' => $cmd,
2560:                 'a' => $args
2561:             );
2562:         }
2563: 
2564:         if ($this->restore !== null)
2565:         {
2566:             $this->last_selector = $this->restore;
2567:             $this->restore = null;
2568:         }
2569: 
2570:         return $this;
2571:     }
2572: 
2573:     /**
2574:      * Set the attribute of a jQuery selector
2575:      *
2576:      * Example:
2577:      *
2578:      * <code>
2579:      * PheryResponse::factory()
2580:      * ->attr('href', 'http://url.com', 'a#link-' . $args['id']);
2581:      * </code>
2582:      *
2583:      * @param string $attr     HTML attribute of the item
2584:      * @param string $data     Value
2585:      * @param string $selector [optional] Provide the jQuery selector directly
2586:      *
2587:      * @return PheryResponse
2588:      */
2589:     public function attr($attr, $data, $selector = null)
2590:     {
2591:         return $this->cmd('attr', array(
2592:             $attr,
2593:             $data
2594:         ), $selector);
2595:     }
2596: 
2597:     /**
2598:      * Trigger the phery:exception event on the calling element
2599:      * with additional data
2600:      *
2601:      * @param string $msg  Message to pass to the exception
2602:      * @param mixed  $data Any data to pass, can be anything
2603:      *
2604:      * @return PheryResponse
2605:      */
2606:     public function exception($msg, $data = null)
2607:     {
2608:         $this->last_selector = null;
2609: 
2610:         return $this->cmd(7, array(
2611:             $msg,
2612:             $data
2613:         ));
2614:     }
2615: 
2616:     /**
2617:      * Call a javascript function.
2618:      * Warning: calling this function will reset the selector jQuery selector previously stated
2619:      *
2620:      * The context of `this` call is the object in the $func_name path or window, if not provided
2621:      *
2622:      * @param string|array $func_name Function name. If you pass a string, it will be accessed on window.func.
2623:      *                                If you pass an array, it will access a member of an object, like array('object', 'property', 'function')
2624:      * @param              mixed      $args,... Any additional arguments to pass to the function
2625:      *
2626:      * @return PheryResponse
2627:      */
2628:     public function call($func_name, $args = null)
2629:     {
2630:         $args = func_get_args();
2631:         array_shift($args);
2632:         $this->last_selector = null;
2633: 
2634:         return $this->cmd(2, array(
2635:             !is_array($func_name) ? array($func_name) : $func_name,
2636:             $args
2637:         ));
2638:     }
2639: 
2640:     /**
2641:      * Call 'apply' on a javascript function.
2642:      * Warning: calling this function will reset the selector jQuery selector previously stated
2643:      *
2644:      * The context of `this` call is the object in the $func_name path or window, if not provided
2645:      *
2646:      * @param string|array $func_name Function name
2647:      * @param array        $args      Any additional arguments to pass to the function
2648:      *
2649:      * @return PheryResponse
2650:      */
2651:     public function apply($func_name, array $args = array())
2652:     {
2653:         $this->last_selector = null;
2654: 
2655:         return $this->cmd(2, array(
2656:             !is_array($func_name) ? array($func_name) : $func_name,
2657:             $args
2658:         ));
2659:     }
2660: 
2661:     /**
2662:      * Clear the selected attribute.
2663:      * Alias for attr('attribute', '')
2664:      *
2665:      * @see attr()
2666:      *
2667:      * @param string $attr     Name of the DOM attribute to clear, such as 'innerHTML', 'style', 'href', etc not the jQuery counterparts
2668:      * @param string $selector [optional] Provide the jQuery selector directly
2669:      *
2670:      * @return PheryResponse
2671:      */
2672:     public function clear($attr, $selector = null)
2673:     {
2674:         return $this->attr($attr, '', $selector);
2675:     }
2676: 
2677:     /**
2678:      * Set the HTML content of an element.
2679:      * Automatically typecasted to string, so classes that
2680:      * respond to __toString() will be converted automatically
2681:      *
2682:      * @param string $content
2683:      * @param string $selector [optional] Provide the jQuery selector directly
2684:      *
2685:      * @return PheryResponse
2686:      */
2687:     public function html($content, $selector = null)
2688:     {
2689:         if (is_array($content))
2690:         {
2691:             $content = join("\n", $content);
2692:         }
2693: 
2694:         return $this->cmd('html', array(
2695:             $this->typecast($content, true, true)
2696:         ), $selector);
2697:     }
2698: 
2699:     /**
2700:      * Set the text of an element.
2701:      * Automatically typecasted to string, so classes that
2702:      * respond to __toString() will be converted automatically
2703:      *
2704:      * @param string $content
2705:      * @param string $selector [optional] Provide the jQuery selector directly
2706:      *
2707:      * @return PheryResponse
2708:      */
2709:     public function text($content, $selector = null)
2710:     {
2711:         if (is_array($content))
2712:         {
2713:             $content = join("\n", $content);
2714:         }
2715: 
2716:         return $this->cmd('text', array(
2717:             $this->typecast($content, true, true)
2718:         ), $selector);
2719:     }
2720: 
2721:     /**
2722:      * Compile a script and call it on-the-fly.
2723:      * There is a closure on the executed function, so
2724:      * to reach out global variables, you need to use window.variable
2725:      * Warning: calling this function will reset the selector jQuery selector previously set
2726:      *
2727:      * @param string|array $script Script content. If provided an array, it will be joined with \n
2728:      *
2729:      * <pre>
2730:      * PheryResponse::factory()
2731:      * ->script(array("if (confirm('Are you really sure?')) $('*').remove()"));
2732:      * </pre>
2733:      *
2734:      * @return PheryResponse
2735:      */
2736:     public function script($script)
2737:     {
2738:         $this->last_selector = null;
2739: 
2740:         if (is_array($script))
2741:         {
2742:             $script = join("\n", $script);
2743:         }
2744: 
2745:         return $this->cmd(3, array(
2746:             $script
2747:         ));
2748:     }
2749: 
2750:     /**
2751:      * Access a global object path
2752:      *
2753:      * @param string|string[] $namespace             For accessing objects, like $.namespace.function() or
2754:      *                                               document.href. if you want to access a global variable,
2755:      *                                               use array('object','property'). You may use a mix of getter/setter
2756:      *                                               to apply a global value to a variable
2757:      *
2758:      * <pre>
2759:      * PheryResponse::factory()->set_var(array('obj','newproperty'),
2760:      *      PheryResponse::factory()->access(array('other_obj','enabled'))
2761:      * );
2762:      * </pre>
2763:      *
2764:      * @param boolean         $new                   Create a new instance of the object, acts like "var v = new JsClass"
2765:      *                                               only works on classes, don't try to use new on a variable or a property
2766:      *                                               that can't be instantiated
2767:      *
2768:      * @return PheryResponse
2769:      */
2770:     public function access($namespace, $new = false)
2771:     {
2772:         $last = $this->set_internal_counter('+');
2773: 
2774:         return $this->cmd(!is_array($namespace) ? array($namespace) : $namespace, array($new, $last));
2775:     }
2776: 
2777:     /**
2778:      * Render a view to the container previously specified
2779:      *
2780:      * @param string $html HTML to be replaced in the container
2781:      * @param array  $data Array of data to pass to the before/after functions set on Phery.view
2782:      *
2783:      * @see Phery.view() on JS
2784:      * @return PheryResponse
2785:      */
2786:     public function render_view($html, $data = array())
2787:     {
2788:         $this->last_selector = null;
2789: 
2790:         if (is_array($html))
2791:         {
2792:             $html = join("\n", $html);
2793:         }
2794: 
2795:         return $this->cmd(5, array(
2796:             $this->typecast($html, true, true),
2797:             $data
2798:         ));
2799:     }
2800: 
2801:     /**
2802:      * Creates a redirect
2803:      *
2804:      * @param string        $url      Complete url with http:// (according to W3 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30)
2805:      * @param bool|string   $view     Internal means that phery will cancel the
2806:      *                                current DOM manipulation and commands and will issue another
2807:      *                                phery.remote to the location in url, useful if your PHP code is
2808:      *                                issuing redirects but you are using AJAX views.
2809:      *                                Passing false will issue a browser redirect
2810:      *
2811:      * @return PheryResponse
2812:      */
2813:     public function redirect($url, $view = false)
2814:     {
2815:         if ($view === false && !preg_match('#^https?\://#i', $url))
2816:         {
2817:             $_url = (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'];
2818:             $start = substr($url, 0, 1);
2819: 
2820:             if (!empty($start))
2821:             {
2822:                 if ($start === '?')
2823:                 {
2824:                     $_url .= str_replace('?' . $_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']);
2825:                 }
2826:                 elseif ($start !== '/')
2827:                 {
2828:                     $_url .= '/';
2829:                 }
2830:             }
2831:             $_url .= $url;
2832:         }
2833:         else
2834:         {
2835:             $_url = $url;
2836:         }
2837: 
2838:         $this->last_selector = null;
2839: 
2840:         if ($view !== false)
2841:         {
2842:             return $this->reset_response()->cmd(8, array(
2843:                 $_url,
2844:                 $view
2845:             ));
2846:         }
2847:         else
2848:         {
2849:             return $this->cmd(8, array(
2850:                 $_url,
2851:                 false
2852:             ));
2853:         }
2854:     }
2855: 
2856:     /**
2857:      * Prepend string/HTML to target(s)
2858:      *
2859:      * @param string $content  Content to be prepended to the selected element
2860:      * @param string $selector [optional] Optional jquery selector string
2861:      *
2862:      * @return PheryResponse
2863:      */
2864:     public function prepend($content, $selector = null)
2865:     {
2866:         if (is_array($content))
2867:         {
2868:             $content = join("\n", $content);
2869:         }
2870: 
2871:         return $this->cmd('prepend', array(
2872:             $this->typecast($content, true, true)
2873:         ), $selector);
2874:     }
2875: 
2876:     /**
2877:      * Clear all the selectors and commands in the current response.
2878:      * @return PheryResponse
2879:      */
2880:     public function reset_response()
2881:     {
2882:         $this->data = array();
2883:         $this->last_selector = null;
2884:         $this->merged = array();
2885:         return $this;
2886:     }
2887: 
2888:     /**
2889:      * Append string/HTML to target(s)
2890:      *
2891:      * @param string $content  Content to be appended to the selected element
2892:      * @param string $selector [optional] Optional jquery selector string
2893:      *
2894:      * @return PheryResponse
2895:      */
2896:     public function append($content, $selector = null)
2897:     {
2898:         if (is_array($content))
2899:         {
2900:             $content = join("\n", $content);
2901:         }
2902: 
2903:         return $this->cmd('append', array(
2904:             $this->typecast($content, true, true)
2905:         ), $selector);
2906:     }
2907: 
2908:     /**
2909:      * Include a stylesheet in the head of the page
2910:      *
2911:      * @param array $path An array of stylesheets, comprising of 'id' => 'path'
2912:      * @param bool $replace Replace any existing ids
2913:      * @return PheryResponse
2914:      */
2915:     public function include_stylesheet(array $path, $replace = false)
2916:     {
2917:         $this->last_selector = null;
2918: 
2919:         return $this->cmd(10, array(
2920:             'c',
2921:             $path,
2922:             $replace
2923:         ));
2924:     }
2925: 
2926:     /**
2927:      * Include a script in the head of the page
2928:      *
2929:      * @param array $path An array of scripts, comprising of 'id' => 'path'
2930:      * @param bool $replace Replace any existing ids
2931:      * @return PheryResponse
2932:      */
2933:     public function include_script($path, $replace = false)
2934:     {
2935:         $this->last_selector = null;
2936: 
2937:         return $this->cmd(10, array(
2938:             'j',
2939:             $path,
2940:             $replace
2941:         ));
2942:     }
2943: 
2944:     /**
2945:      * Magically map to any additional jQuery function.
2946:      * To reach this magically called functions, the jquery() selector must be called prior
2947:      * to any jquery specific call
2948:      *
2949:      * @param string $name
2950:      * @param array $arguments
2951:      *
2952:      * @see jquery()
2953:      * @see j()
2954:      * @return PheryResponse
2955:      */
2956:     public function __call($name, $arguments)
2957:     {
2958:         if ($this->last_selector)
2959:         {
2960:             if (count($arguments))
2961:             {
2962:                 foreach ($arguments as $_name => $argument)
2963:                 {
2964:                     $arguments[$_name] = $this->typecast($argument, true, true);
2965:                 }
2966: 
2967:                 $this->cmd($name, $arguments);
2968:             }
2969:             else
2970:             {
2971:                 $this->cmd($name);
2972:             }
2973: 
2974:         }
2975: 
2976:         return $this;
2977:     }
2978: 
2979:     /**
2980:      * Magic functions
2981:      *
2982:      * @param string $name
2983:      * @return PheryResponse
2984:      */
2985:     function __get($name)
2986:     {
2987:         $name = strtolower($name);
2988: 
2989:         if ($name === 'this')
2990:         {
2991:             $this->set_internal_counter('~');
2992:         }
2993:         elseif ($name === 'document')
2994:         {
2995:             $this->jquery('document');
2996:         }
2997:         elseif ($name === 'window')
2998:         {
2999:             $this->jquery('window');
3000:         }
3001:         elseif ($name === 'jquery')
3002:         {
3003:             $this->set_internal_counter('#');
3004:         }
3005:         else
3006:         {
3007:             $this->access($name);
3008:         }
3009: 
3010:         return $this;
3011:     }
3012: 
3013:     /**
3014:      * Convert, to a maximum depth, nested responses, and typecast int properly
3015:      *
3016:      * @param mixed $argument The value
3017:      * @param bool $toString Call class __toString() if possible, and typecast int correctly
3018:      * @param bool $nested Should it look for nested arrays and classes?
3019:      * @param int $depth Max depth
3020:      * @return mixed
3021:      */
3022:     protected function typecast($argument, $toString = true, $nested = false, $depth = 4)
3023:     {
3024:         if ($nested)
3025:         {
3026:             $depth--;
3027:             if ($argument instanceof PheryResponse)
3028:             {
3029:                 $argument = array('PR' => $argument->process_merged());
3030:             }
3031:             elseif ($argument instanceof PheryFunction)
3032:             {
3033:                 $argument = array('PF' => $argument->compile());
3034:             }
3035:             elseif ($depth > 0 && is_array($argument))
3036:             {
3037:                 foreach ($argument as $name => $arg) {
3038:                     $argument[$name] = $this->typecast($arg, $toString, $nested, $depth);
3039:                 }
3040:             }
3041:         }
3042: 
3043:         if ($toString && !empty($argument))
3044:         {
3045:             if (is_string($argument) && ctype_digit($argument))
3046:             {
3047:                 if ($this->config['convert_integers'] === true)
3048:                 {
3049:                     $argument = (int)$argument;
3050:                 }
3051:             }
3052:             elseif (is_object($argument) && $this->config['typecast_objects'] === true)
3053:             {
3054:                 $class = get_class($argument);
3055:                 if ($class !== false)
3056:                 {
3057:                     $rc = new ReflectionClass(get_class($argument));
3058:                     if ($rc->hasMethod('__toString'))
3059:                     {
3060:                         $argument = "{$argument}";
3061:                     }
3062:                     else
3063:                     {
3064:                         $argument = json_decode(json_encode($argument), true);
3065:                     }
3066:                 }
3067:                 else
3068:                 {
3069:                     $argument = json_decode(json_encode($argument), true);
3070:                 }
3071:             }
3072:         }
3073: 
3074:         return $argument;
3075:     }
3076: 
3077:     /**
3078:      * Process merged responses
3079:      * @return array
3080:      */
3081:     protected function process_merged()
3082:     {
3083:         $data = $this->data;
3084: 
3085:         if (empty($data) && $this->last_selector !== null && !$this->is_special_selector('#'))
3086:         {
3087:             $data[$this->last_selector] = array();
3088:         }
3089: 
3090:         foreach ($this->merged as $r)
3091:         {
3092:             foreach ($r as $selector => $response)
3093:             {
3094:                 if (!ctype_digit($selector))
3095:                 {
3096:                     if (isset($data[$selector]))
3097:                     {
3098:                         $data[$selector] = array_merge_recursive($data[$selector], $response);
3099:                     }
3100:                     else
3101:                     {
3102:                         $data[$selector] = $response;
3103:                     }
3104:                 }
3105:                 else
3106:                 {
3107:                     $selector = (int)$selector;
3108:                     while (isset($data['0'.$selector]))
3109:                     {
3110:                         $selector++;
3111:                     }
3112:                     $data['0'.$selector] = $response;
3113:                 }
3114:             }
3115:         }
3116: 
3117:         return $data;
3118:     }
3119: 
3120:     /**
3121:      * Return the JSON encoded data
3122:      * @return string
3123:      */
3124:     public function render()
3125:     {
3126:         return json_encode((object)$this->process_merged());
3127:     }
3128: 
3129:     /**
3130:      * Output the current answer as a load directive, as a ready-to-use string
3131:      *
3132:      * <code>
3133:      *
3134:      * </code>
3135:      *
3136:      * @param bool $echo Automatically echo the javascript instead of returning it
3137:      * @return string
3138:      */
3139:     public function inline_load($echo = false)
3140:     {
3141:         $body = addcslashes($this->render(), "\\'");
3142: 
3143:         $javascript = "phery.load('{$body}');";
3144: 
3145:         if ($echo)
3146:         {
3147:             echo $javascript;
3148:         }
3149: 
3150:         return $javascript;
3151:     }
3152: 
3153:     /**
3154:      * Return the JSON encoded data
3155:      * if the object is typecasted as a string
3156:      * @return string
3157:      */
3158:     public function __toString()
3159:     {
3160:         return $this->render();
3161:     }
3162: 
3163:     /**
3164:      * Initialize the instance from a serialized state
3165:      *
3166:      * @param string $serialized
3167:      * @throws PheryException
3168:      * @return PheryResponse
3169:      */
3170:     public function unserialize($serialized)
3171:     {
3172:         $obj = json_decode($serialized, true);
3173:         if ($obj && is_array($obj) && json_last_error() === JSON_ERROR_NONE)
3174:         {
3175:             $this->exchangeArray($obj['this']);
3176:             $this->data = (array)$obj['data'];
3177:             $this->set_response_name((string)$obj['name']);
3178:             $this->merged = (array)$obj['merged'];
3179:         }
3180:         else
3181:         {
3182:             throw new PheryException('Invalid data passed to unserialize');
3183:         }
3184:         return $this;
3185:     }
3186: 
3187:     /**
3188:      * Serialize the response in JSON
3189:      * @return string|bool
3190:      */
3191:     public function serialize()
3192:     {
3193:         return json_encode(array(
3194:             'data' => $this->data,
3195:             'this' => $this->getArrayCopy(),
3196:             'name' => $this->name,
3197:             'merged' => $this->merged,
3198:         ));
3199:     }
3200: 
3201:     /**
3202:      * Determine if the last selector or the selector provided is an special
3203:      *
3204:      * @param string $type
3205:      * @param string $selector
3206:      * @return boolean
3207:      */
3208:     protected function is_special_selector($type = null, $selector = null)
3209:     {
3210:         $selector = Phery::coalesce($selector, $this->last_selector);
3211: 
3212:         if ($selector && preg_match('/\{([\D]+)\d+\}/', $selector, $matches))
3213:         {
3214:             if ($type === null)
3215:             {
3216:                 return true;
3217:             }
3218: 
3219:             return ($matches[1] === $type);
3220:         }
3221: 
3222:         return false;
3223:     }
3224: }
3225: 
3226: /**
3227:  * Create an anonymous function for use on Javascript callbacks
3228:  * @package    Phery
3229:  */
3230: class PheryFunction {
3231: 
3232:     /**
3233:      * Parameters that will be replaced inside the response
3234:      * @var array
3235:      */
3236:     protected $parameters = array();
3237:     /**
3238:      * The function string itself
3239:      * @var array
3240:      */
3241:     protected $value = null;
3242: 
3243:     /**
3244:      * Sets new raw parameter to be passed, that will be eval'ed.
3245:      * If you don't pass the function(){ } it will be appended
3246:      *
3247:      * <code>
3248:      * $raw = new PheryFunction('function($val){ return $val; }');
3249:      * // or
3250:      * $raw = new PheryFunction('alert("done");'); // turns into function(){ alert("done"); }
3251:      * </code>
3252:      *
3253:      * @param   string|array  $value      Raw function string. If you pass an array,
3254:      *                                    it will be joined with a line feed \n
3255:      * @param   array         $parameters You can pass parameters that will be replaced
3256:      *                                    in the $value when compiling
3257:      */
3258:     public function __construct($value, $parameters = array())
3259:     {
3260:         if (!empty($value))
3261:         {
3262:             // Set the expression string
3263:             if (is_array($value))
3264:             {
3265:                 $this->value = join("\n", $value);
3266:             }
3267:             elseif (is_string($value))
3268:             {
3269:                 $this->value = $value;
3270:             }
3271: 
3272:             if (!preg_match('/^\s*function/im', $this->value))
3273:             {
3274:                 $this->value = 'function(){' . $this->value . '}';
3275:             }
3276: 
3277:             $this->parameters = $parameters;
3278:         }
3279:     }
3280: 
3281:     /**
3282:      * Bind a variable to a parameter.
3283:      *
3284:      * @param   string  $param  parameter key to replace
3285:      * @param   mixed   $var    variable to use
3286:      * @return  PheryFunction
3287:      */
3288:     public function bind($param, & $var)
3289:     {
3290:         $this->parameters[$param] =& $var;
3291: 
3292:         return $this;
3293:     }
3294: 
3295:     /**
3296:      * Set the value of a parameter.
3297:      *
3298:      * @param   string  $param  parameter key to replace
3299:      * @param   mixed   $value  value to use
3300:      * @return  PheryFunction
3301:      */
3302:     public function param($param, $value)
3303:     {
3304:         $this->parameters[$param] = $value;
3305: 
3306:         return $this;
3307:     }
3308: 
3309:     /**
3310:      * Add multiple parameter values.
3311:      *
3312:      * @param   array   $params list of parameter values
3313:      * @return  PheryFunction
3314:      */
3315:     public function parameters(array $params)
3316:     {
3317:         $this->parameters = $params + $this->parameters;
3318: 
3319:         return $this;
3320:     }
3321: 
3322:     /**
3323:      * Get the value as a string.
3324:      *
3325:      * @return  string
3326:      */
3327:     public function value()
3328:     {
3329:         return (string) $this->value;
3330:     }
3331: 
3332:     /**
3333:      * Return the value of the expression as a string.
3334:      *
3335:      * <code>
3336:      *     echo $expression;
3337:      * </code>
3338:      *
3339:      * @return  string
3340:      */
3341:     public function __toString()
3342:     {
3343:         return $this->value();
3344:     }
3345: 
3346:     /**
3347:      * Compile function and return it. Replaces any parameters with
3348:      * their given values.
3349:      *
3350:      * @return  string
3351:      */
3352:     public function compile()
3353:     {
3354:         $value = $this->value();
3355: 
3356:         if ( ! empty($this->parameters))
3357:         {
3358:             $params = $this->parameters;
3359:             $value = strtr($value, $params);
3360:         }
3361: 
3362:         return $value;
3363:     }
3364: 
3365:     /**
3366:      * Static instantation for PheryFunction
3367:      *
3368:      * @param string|array $value
3369:      * @param array        $parameters
3370:      *
3371:      * @return PheryFunction
3372:      */
3373:     public static function factory($value, $parameters = array())
3374:     {
3375:         return new PheryFunction($value, $parameters);
3376:     }
3377: }
3378: 
3379: /**
3380:  * Exception class for Phery specific exceptions
3381:  * @package    Phery
3382:  */
3383: class PheryException extends Exception {
3384: 
3385: }
3386: 
Phery API documentation generated by ApiGen 2.8.0