Source for file phery.php

Documentation is available at phery.php

  1. <?php
  2. /**
  3.  * PHP + jQuery + AJAX = phery
  4.  * Copyright (C) 2011 gahgneh
  5.  *
  6.  * This program is free software: you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation, either version 3 of the License, or
  9.  * (at your option) any later version.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18.  *
  19.  * @package phery_package
  20.  * @link https://github.com/gahgneh/phery
  21.  * @author gahgneh
  22.  * @version 0.6 beta
  23.  * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
  24.  */
  25.  
  26. /**
  27.  * Main class
  28.  * @package phery_package
  29.  * @subpackage phery
  30.  */
  31. class phery {
  32.  
  33.     /**
  34.      * The functions registered
  35.      * @var array 
  36.      */
  37.     private $functions array();
  38.     /**
  39.      * The callbacks registered
  40.      * @var array 
  41.      */
  42.     private $callbacks array();
  43.     /**
  44.      * The callback data to be passed to callbacks and responses
  45.      * @var array 
  46.      */
  47.     private $data array();
  48.     /**
  49.      * Static instance for singleton
  50.      * @staticvar phery $instance 
  51.      */
  52.     private static $instance null;
  53.     /**
  54.      * Will call the functions defined in this variable even
  55.      * if it wasn't sent by AJAX, use it wisely. (good for SEO though)
  56.      * @var array 
  57.      */
  58.     public $unobstructive = array();
  59.     /**
  60.      * Hold the answers for answer_for function
  61.      * @see phery::answer_for
  62.      * @var array 
  63.      */
  64.     private $answers array();
  65.     /**
  66.      * Config
  67.      * <code>
  68.      * 'exit_allowed',
  69.      * 'no_stripslashes',
  70.      * 'exceptions',
  71.      * 'unobstructive',
  72.      * </code>
  73.      * @var array 
  74.      * @see config()
  75.      */
  76.     public $config = null;
  77.  
  78.     /**
  79.      * Construct the new phery instance
  80.      */
  81.     function __construct($config null)
  82.     {
  83.         $this->callbacks array(
  84.             'pre' => array(),
  85.             'post' => array()
  86.         );
  87.  
  88.         $this->config = array(
  89.             'exit_allowed' => true,
  90.             'no_stripslashes' => false,
  91.             'exceptions' => false,
  92.             'unobstructive' => array(),
  93.         );
  94.  
  95.         if (isset($config))
  96.         {
  97.             $this->config($config);
  98.         }
  99.     }
  100.  
  101.     /**
  102.      * Set callbacks for pre and post filters.
  103.      * Callbacks are useful for example, if you have 2 or more AJAX functions, and you need to perform
  104.      * the same data manipulation, like removing an 'id' from the $_POST['args'], or to check for potential
  105.      * CSRF or SQL injection attempts on all the functions, clean data or perform START TRANSACTION for database, etc
  106.      * @param array $callbacks 
  107.      *  <code>
  108.      *  array(
  109.      *  // Set a function to be called BEFORE
  110.      *  // processing the request, if it's an
  111.      *  // AJAX to be processed request, can be
  112.      *  // an array of callbacks
  113.      *  'pre' => array|function,
  114.      *  // Set a function to be called AFTER
  115.      *  // processing the request, if it's an AJAX
  116.      *  // processed request, can be an array of
  117.      *  // callbacks
  118.      *  'post' => array|function
  119.      *  );
  120.      *  </code>
  121.      *  The callback function should be
  122.      *  <code>
  123.      *  // $additional_args is passed using the callback_data() function, in this case, a pre callback
  124.      *  function pre_callback($ajax_data, $internal_data){
  125.      *    // Do stuff
  126.      *    $_POST['args']['id'] = $additional_args['id'];
  127.      *    return true;
  128.      *  }
  129.      *  // post callback would be to save the data perhaps? Just to keep the code D.R.Y.
  130.      *  function post_callback($ajax_data, $internal_data){
  131.      *    $this->database->save();
  132.      *    return true;
  133.      *  }
  134.      *  </code>
  135.      *  Returning false on the callback will make the process() phase to RETURN, but won't exit.
  136.      *  You may manually exit on the post callback if desired
  137.      *  Any data that should be modified will be inside $_POST['args'] (can be accessed freely on 'pre',
  138.      *  will be passed to the AJAX function)
  139.      * @return phery 
  140.      */
  141.     function callback(array $callbacks)
  142.     {
  143.         if (isset($callbacks['pre']))
  144.         {
  145.             if (is_array($callbacks['pre']&& !is_callable($callbacks['pre']))
  146.             {
  147.                 foreach ($callbacks['pre'as $func)
  148.                 {
  149.                     if (is_callable($func))
  150.                     {
  151.                         $this->callbacks['pre'][$func;
  152.                     }
  153.                     else
  154.                     {
  155.                         if ($this->config['exceptions'=== truethrow new phery_exception("The provided pre callback function isn't callable");
  156.                     }
  157.                 }
  158.             }
  159.             else
  160.             {
  161.                 if (is_callable($callbacks['pre']))
  162.                 {
  163.                     $this->callbacks['pre'][$callbacks['pre'];
  164.                 }
  165.                 else
  166.                 {
  167.                     if ($this->config['exceptions'=== truethrow new phery_exception("The provided pre callback function isn't callable");
  168.                 }
  169.             }
  170.         }
  171.  
  172.         if (isset($callbacks['post']))
  173.         {
  174.             if (is_array($callbacks['post']&& !is_callable($callbacks['post']))
  175.             {
  176.                 foreach ($callbacks['post'as $func)
  177.                 {
  178.                     if (is_callable($func))
  179.                     {
  180.                         $this->callbacks['post'][$func;
  181.                     }
  182.                     else
  183.                     {
  184.                         if ($this->config['exceptions'=== truethrow new phery_exception("The provided post callback function isn't callable");
  185.                     }
  186.                 }
  187.             }
  188.             else
  189.             {
  190.                 if (is_callable($callbacks['post']))
  191.                 {
  192.                     $this->callbacks['post'][$callbacks['post'];
  193.                 }
  194.                 else
  195.                 {
  196.                     if ($this->config['exceptions'=== truethrow new phery_exception("The provided post callback function isn't callable");
  197.                 }
  198.             }
  199.         }
  200.  
  201.         return $this;
  202.     }
  203.  
  204.     /**
  205.      * Set any data to pass to the callbacks
  206.      * @param mixed $args,... Parameters, can be anything
  207.      * @return phery 
  208.      */
  209.     function data($args)
  210.     {
  211.         $this->data = array_merge_recursive(func_get_args()$this->data);
  212.         return $this;
  213.     }
  214.     
  215.     /**
  216.      * Encode PHP code to put inside data-args, usually for updating the data there
  217.      * @param mixed $data Any data that can be converted using json_encode
  218.      * @return string Return json_encode'd and htmlentities'd string
  219.      */
  220.     static function args($data$encoding 'UTF-8')
  221.     {
  222.         return htmlentities(json_encode($data)ENT_COMPAT$encodingfalse);
  223.     }
  224.     
  225.     /**
  226.      * Check if the current call is an ajax call
  227.      * @static
  228.      * @return bool 
  229.      */
  230.     static function is_ajax()
  231.     {
  232.         return (bool) (isset($_SERVER['HTTP_X_REQUESTED_WITH']&&
  233.         strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH']'XMLHttpRequest'=== &&
  234.         strtoupper($_SERVER['REQUEST_METHOD']=== 'POST');
  235.     }
  236.  
  237.     private function strip_slashes_recursive($variable)
  238.     {
  239.         if (is_string($variable))
  240.         {
  241.             return stripslashes($variable);
  242.         }
  243.  
  244.         if (is_array($variable))
  245.         {
  246.             foreach ($variable as $i => $value)
  247.             {
  248.                 $variable[$i$this->strip_slashes_recursive($value);
  249.             }
  250.         }
  251.  
  252.         return $variable;
  253.     }
  254.  
  255.     /**
  256.      * Return the data associatated with a processed unobstructive POST call
  257.      * @param string $alias The name of the alias for the process function
  258.      * @param mixed $default Any data that should be returned if there's no answer, defaults to null
  259.      * @return mixed Return $default if no data available, defaults to NULL
  260.      */
  261.     function answer_for($alias$default NULL)
  262.     {
  263.         if (isset($this->answers[$alias]&& !empty($this->answers[$alias]))
  264.         {
  265.             return $this->answers[$alias];
  266.         }
  267.         return $default;
  268.     }
  269.  
  270.     private function _process($unobstructive$last_call)
  271.     {
  272.         $response null;
  273.  
  274.         if isset($_POST['remote'])) {
  275.             if ($this->config['exceptions'])
  276.                 throw new phery_exception('AJAX request without remote defined');
  277.             return;
  278.         }
  279.  
  280.         if (isset($_GET['_'])){
  281.             $this->data['requested'$_GET['_'];
  282.             unset($_GET['_']);
  283.         }
  284.  
  285.         $args array();
  286.         $remote $_POST['remote'];
  287.  
  288.         if (isset($_POST['submit_id']))
  289.         {
  290.             $this->data['submit_id''#'."{$_POST['submit_id']}";
  291.         }
  292.  
  293.         $this->data['remote'$remote;
  294.  
  295.         if ($unobstructive === true)
  296.         {
  297.             if ($this->config['no_stripslashes'=== false)
  298.             {
  299.                 $args $this->strip_slashes_recursive($_POST);
  300.             }
  301.             else
  302.             {
  303.                 $args $_POST;
  304.             }
  305.  
  306.             unset($args['remote']);
  307.         }
  308.         else
  309.         {
  310.             if (isset($_POST['args']))
  311.             {
  312.                 if ($this->config['no_stripslashes'=== false)
  313.                 {
  314.                     $args $this->strip_slashes_recursive($_POST['args']);
  315.                 }
  316.                 else
  317.                 {
  318.                     $args $_POST['args'];
  319.                 }
  320.  
  321.                 if ($last_call === trueunset($_POST['args']);
  322.             }
  323.         }
  324.  
  325.         foreach ($this->callbacks['pre'as $func)
  326.         {
  327.             if (($args call_user_func($func$args$this->data)) === falsereturn;
  328.         }
  329.  
  330.  
  331.         if (isset($this->functions[$remote]))
  332.         {
  333.             unset($_POST['remote']);
  334.  
  335.             $response call_user_func($this->functions[$remote]$args$this->data);
  336.  
  337.             foreach ($this->callbacks['post'as $func)
  338.             {
  339.                 if (call_user_func($func$args$this->data$response=== falsereturn;
  340.             }
  341.  
  342.             $_POST['remote'$remote;
  343.  
  344.             if ($unobstructive === false)
  345.             {
  346.                 if ($response instanceof phery_response)
  347.                 {
  348.                     header("Cache-Control: no-cache, must-revalidate");
  349.                     header("Expires: 0");
  350.                     header('Content-Type: application/json');
  351.                 }
  352.                 echo $response;
  353.             }
  354.             else
  355.             {
  356.                 $this->answers[$remote$response;
  357.             }
  358.         }
  359.         else
  360.         {
  361.             if ($this->config['exceptions'&& $last_call === true)
  362.                 throw new phery_exception('No function "'.$remote.'" set');
  363.         }
  364.  
  365.         if ($unobstructive === false)
  366.         {
  367.             if ($this->config['exit_allowed'=== true)
  368.             {
  369.                 if ($last_call || $response !== nullexit;
  370.             }
  371.         }
  372.     }
  373.  
  374.     /**
  375.      * Process the AJAX requests if any
  376.      * @param bool $last_call Set this to false if any other further calls to process() will happen, otherwise it will exit
  377.      */
  378.     function process($last_call true)
  379.     {
  380.         if (self::is_ajax())
  381.         {
  382.             // AJAX call
  383.             $this->_process(false$last_call);
  384.         }
  385.         elseif (strtoupper($_SERVER['REQUEST_METHOD']== 'POST' &&
  386.             isset($_POST['remote']&&
  387.             in_array($_POST['remote']$this->unobstructive))
  388.         {
  389.             // Regular processing, unobstrutive post, pass the $_POST variable to the function anyway
  390.             $this->_process(true$last_call);
  391.         }
  392.     }
  393.  
  394.     /**
  395.      * Config the current instance of phery
  396.      * @param array $config Associative array containing the following options
  397.      *  <code>
  398.      *  array(
  399.      *  // Defaults to true, stop further script execution
  400.      *  'exit_allowed' => true/false,
  401.      *  // Don't apply stripslashes on the args
  402.      *  'no_stripslashes' => true/false,
  403.      *  // Throw exceptions on errors
  404.      *  'exceptions' => true/false,
  405.      *  // Set the functions that will be called even if is a
  406.      *  // POST but not an AJAX call
  407.      *  'unobstructive' => array('function-alias-1','function-alias-2')
  408.      *  );
  409.      *  </code>
  410.      * @return phery 
  411.      */
  412.     function config(array $config)
  413.     {
  414.         if (is_array($config))
  415.         {
  416.             if (isset($config['exit_allowed']))
  417.             {
  418.                 $this->config['exit_allowed'= (bool) $config['exit_allowed'];
  419.             }
  420.             if (isset($config['no_stripslashes']))
  421.             {
  422.                 $this->config['no_stripslashes'= (bool) $config['no_stripslashes'];
  423.             }
  424.             if (isset($config['exceptions']))
  425.             {
  426.                 $this->config['exceptions'= (bool) $config['exceptions'];
  427.             }
  428.             if (isset($config['unobstructive']&& is_array($config['unobstructive']))
  429.             {
  430.                 $this->unobstructive array_merge_recursive(
  431.                         $this->unobstructive,
  432.                         $config['unobstructive']
  433.                 );
  434.             }
  435.         }
  436.         return $this;
  437.     }
  438.  
  439.     /**
  440.      * Generates just one instance. Useful to use in many included files. Chainable
  441.      * @param array $config Associative config array
  442.      * @see __construct()
  443.      * @see phery::config()
  444.      * @static
  445.      * @return phery 
  446.      */
  447.     static function instance(array $config null)
  448.     {
  449.         if (!(self::$instance instanceof phery))
  450.         {
  451.             self::$instance new phery($config);
  452.         }
  453.         return self::$instance;
  454.     }
  455.  
  456.     /**
  457.      * Sets the functions to respond to the ajax call.
  458.      * For security reasons, these functions should not be available for direct POST/GET requests.
  459.      * These will be set only for AJAX requests as it will only be called in case of an ajax request,
  460.      * to save resources.
  461.      * The answer/process function, must necessarily have the following structure:
  462.      * <code>
  463.      * function func($ajax_data, $callback_data){
  464.      *   $r = new phery_response; // or phery_response::factory();
  465.      *   // Sometimes the $callback_data will have an item called 'submit_id',
  466.      *   // is the ID of the calling DOM element.
  467.      *   // if (isset($callback_data['submit_id'])) {  }
  468.      *   $r->jquery('#id')->animate(...);
  469.      *      return $r;
  470.      * }
  471.      * </code>
  472.      * @param array $functions An array of functions to register to the instance.
  473.      * @return phery 
  474.      */
  475.     function set(array $functions)
  476.     {
  477.         if (strtoupper($_SERVER['REQUEST_METHOD']!= 'POST' && !isset($_POST['remote'])) return $this;
  478.  
  479.         if (isset($functions&& is_array($functions))
  480.         {
  481.             foreach ($functions as $name => $func)
  482.             {
  483.                 if (is_callable($func))
  484.                 {
  485.                     if (isset($this->functions[$name]))
  486.                     {
  487.                         if ($this->config['exceptions']throw new phery_exception('The function "'.$name.'" already exists and was rewritten');
  488.                     }
  489.                     $this->functions[$name$func;
  490.                 }
  491.                 else
  492.                 {
  493.                     if ($this->config['exceptions'])
  494.                             throw new phery_exception('Provided function "'.$name.'" isnt a valid function or method');
  495.                 }
  496.             }
  497.         }
  498.         else
  499.         {
  500.             if ($this->config['exceptions']throw new phery_exception('Call to "set" must be provided an array');
  501.         }
  502.         return $this;
  503.     }
  504.  
  505.     /**
  506.      * Create a new instance of phery that can be chained, without the need of assigning it to a variable
  507.      * @param array $config Associative config array
  508.      * @see phery::config()
  509.      * @static
  510.      * @return phery 
  511.      */
  512.     static function factory(array $config null)
  513.     {
  514.         return new phery($config);
  515.     
  516.  
  517.     /**
  518.      * Helper function that generates an ajax link, defaults to "A" tag
  519.      * @param string $title The content of the link
  520.      * @param string $function The PHP function assigned name on phery::set()
  521.      * @param array $attributes Extra attributes that can be passed to the link, like class, style, etc
  522.      *  <code>
  523.      *  array(
  524.      *  // Display confirmation on click
  525.      *  'confirm' => 'Are you sure?',
  526.      *  // The tag for the item, defaults to a
  527.      *  'tag' => 'a',
  528.      *  // Define another URI for the AJAX call, this defines the HREF of A
  529.      *  'href' => '/path/to/url',
  530.      *  // Extra arguments to pass to the AJAX function, will be stored
  531.      *  // in the args attribute as a JSON notation
  532.      *  'args' => array(1, "a"),
  533.      *  // Set the "href" attriute for non-anchor (a) AJAX tags (like buttons or spans).
  534.      *  // Works for A links too, it won't function without javascript
  535.      *  'target' => '/default/ajax/controller',
  536.      *  // Define the data-type for the communication
  537.      *  'data-type' => 'json'
  538.      *  // Set the encoding of the data, defaults to UTF-8
  539.      *  'encoding' => 'UTF-8',
  540.      *  );
  541.      *  </code>
  542.      * @param phery $phery Pass the current instance of phery, so it can check if the
  543.      *  functions are defined, and throw exceptions
  544.      * @static
  545.      * @return string The mounted HTML tag
  546.      */
  547.     static function link_to($title$functionarray $attributes array()phery $phery null)
  548.     {
  549.         if ($function == '')
  550.         {
  551.             if ($phery)
  552.             {
  553.                 if ($phery->config['exceptions']throw new phery_exception('The "function" argument must be provided to "link_to"');
  554.             }
  555.             return '';
  556.         }
  557.         if ($phery)
  558.         {
  559.             if (!isset($phery->functions[$function]))
  560.             {
  561.                 if ($phery->config['exceptions'])
  562.                         throw new phery_exception('The function "'.$function.'" provided in "link_to" hasnt been set');
  563.             }
  564.         }
  565.  
  566.         $tag 'a';
  567.         if (isset($attributes['tag']))
  568.         {
  569.             $tag $attributes['tag'];
  570.             unset($attributes['tag']);
  571.         }
  572.  
  573.         if (isset($attributes['target']))
  574.         {
  575.             $attributes['data-target'$attributes['target'];
  576.             unset($attributes['target']);
  577.         }
  578.  
  579.         if (isset($attributes['args']))
  580.         {
  581.             $attributes['data-args'json_encode($attributes['args']);
  582.             unset($attributes['args']);
  583.         }
  584.  
  585.         if (isset($attributes['confirm']))
  586.         {
  587.             $attributes['data-confirm'$attributes['confirm'];
  588.             unset($attributes['confirm']);
  589.         }
  590.         
  591.         $encoding 'UTF-8';
  592.         if (isset($attributes['encoding']))
  593.         {
  594.             $encoding $attributes['encoding'];
  595.             unset($attributes['encoding']);
  596.         }
  597.  
  598.         $attributes['data-remote'$function;
  599.  
  600.         $ret array();
  601.         $ret["<{$tag}";
  602.         foreach ($attributes as $attribute => $value)
  603.         {
  604.             $ret["{$attribute}=\"".htmlentities($valueENT_COMPAT$encodingfalse)."\"";
  605.         }
  606.         $ret[">{$title}</{$tag}>";
  607.         return join(' '$ret);
  608.     }
  609.  
  610.     /**
  611.      * Create a <form> tag with ajax enabled. Must be closed manually with </form>
  612.      * @param string $action where to go, can be empty
  613.      * @param string $function Registered function name
  614.      * @param array $attributes 
  615.      *  <code>
  616.      *  array(
  617.      *  // Confirmation dialog
  618.      *  'confirm' => 'Are you sure?',
  619.      *  // Type of call, defaults to JSON (to use phery_response)
  620.      *  'data-type' => 'json',
  621.      *  // 'all' submits all elements on the form, even
  622.      *  // if empty or not checked, disabled also submit disabled elements
  623.      *  'submit' => array('all' => true, 'disabled' => true)
  624.      *  // Set the encoding of the data, defaults to UTF-8
  625.      *  'encoding' => 'UTF-8',
  626.      *  );
  627.      *  </code>
  628.      * @param phery $phery Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
  629.      * @static
  630.      * @return string The mounted <form> HTML tag
  631.      */
  632.     static function form_for($action$functionarray $attributes array()phery $phery null)
  633.     {
  634.         if ($function == '')
  635.         {
  636.             if ($phery)
  637.             {
  638.                 if ($phery->config['exceptions']throw new phery_exception('The "function" argument must be provided to "form_for"');
  639.             }
  640.             return '';
  641.         }
  642.         if ($phery)
  643.         {
  644.             if (!isset($phery->functions[$function]))
  645.             {
  646.                 if ($phery->config['exceptions'])
  647.                         throw new phery_exception('The function "'.$function.'" provided in "form_for" hasnt been set');
  648.             }
  649.         }
  650.  
  651.         if (isset($attributes['args']))
  652.         {
  653.             $attributes['data-args'json_encode($attributes['args']);
  654.             unset($attributes['args']);
  655.         }
  656.  
  657.         if (isset($attributes['confirm']))
  658.         {
  659.             $attributes['data-confirm'$attributes['confirm'];
  660.             unset($attributes['confirm']);
  661.         }
  662.  
  663.         if (isset($attributes['submit']))
  664.         {
  665.             $attributes['data-submit'json_encode($attributes['submit']);
  666.             unset($attributes['submit']);
  667.         }
  668.         
  669.         $encoding 'UTF-8';
  670.         if (isset($attributes['encoding']))
  671.         {
  672.             $encoding $attributes['encoding'];
  673.             unset($attributes['encoding']);
  674.         }
  675.  
  676.         $ret array();
  677.         $ret['<form method="POST" action="'.$action.'" data-remote="'.$function.'"';
  678.         foreach ($attributes as $attribute => $value)
  679.         {
  680.             $ret["{$attribute}=\"".htmlentities($valueENT_COMPAT$encodingfalse)."\"";
  681.         }
  682.         $ret['><input type="hidden" name="remote" value="'.$function.'"/>';
  683.         return join(' '$ret);
  684.     }
  685.     
  686.     /**
  687.      * Create a <select> element with ajax enabled on "change" event.
  688.      * @param string $function Registered function name
  689.      * @param array $items Options for the select, 'value' => 'text' representation
  690.      * @param array $attributes 
  691.      *  <code>
  692.      *  array(
  693.      *  // Confirmation dialog
  694.      *  'confirm' => 'Are you sure?',
  695.      *  // Type of call, defaults to JSON (to use phery_response)
  696.      *  'data-type' => 'json',
  697.      *  // The URL where it should call
  698.      *  'target' => '/path/to/php',
  699.      *  // Extra arguments to pass to the AJAX function, will be stored
  700.      *  // in the args attribute as a JSON notation
  701.      *  'args' => array(1, "a"),
  702.      *  // Set the encoding of the data, defaults to UTF-8
  703.      *  'encoding' => 'UTF-8',
  704.      *  // The current selected value, or array(1,2) for multiple
  705.      *  'selected' => 1
  706.      *  );
  707.      *  </code>
  708.      * @param phery $phery Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
  709.      * @static
  710.      * @return string The mounted <select> with <option>s inside
  711.      */
  712.     static function select_for($functionarray $itemsarray $attributes array()phery $phery null)
  713.     {
  714.         if ($function == '')
  715.         {
  716.             if ($phery)
  717.             {
  718.                 if ($phery->config['exceptions']throw new phery_exception('The "function" argument must be provided to "select_for"');
  719.             }
  720.             return '';
  721.         }
  722.         if ($phery)
  723.         {
  724.             if (!isset($phery->functions[$function]))
  725.             {
  726.                 if ($phery->config['exceptions'])
  727.                         throw new phery_exception('The function "'.$function.'" provided in "select_for" hasnt been set');
  728.             }
  729.         }
  730.  
  731.         if (isset($attributes['args']))
  732.         {
  733.             $attributes['data-args'json_encode($attributes['args']);
  734.             unset($attributes['args']);
  735.         }
  736.  
  737.         if (isset($attributes['confirm']))
  738.         {
  739.             $attributes['data-confirm'$attributes['confirm'];
  740.             unset($attributes['confirm']);
  741.         }
  742.         
  743.         $encoding 'UTF-8';
  744.         if (isset($attributes['encoding']))
  745.         {
  746.             $encoding $attributes['encoding'];
  747.             unset($attributes['encoding']);
  748.         }
  749.         
  750.         $selected array();
  751.         if (isset($attributes['selected']))
  752.         {
  753.             if (is_array($attributes['selected']))
  754.             {
  755.                 // multiple select
  756.                 $selected $attributes['selected'];
  757.             }
  758.             else
  759.             {
  760.                 // single select
  761.                 $selected array($attributes['selected']);
  762.             }
  763.             unset($attributes['selected']);
  764.         }
  765.         
  766.         if (isset($attributes['multiple']))
  767.         {
  768.             $attributes['multiple''multiple';
  769.         }
  770.         
  771.         $ret array();
  772.         $ret['<select data-remote="'.$function.'"';
  773.         foreach ($attributes as $attribute => $value)
  774.         {
  775.             $ret["{$attribute}=\"".htmlentities($valueENT_COMPAT$encodingfalse)."\"";
  776.         }
  777.         $ret['>';
  778.         
  779.         foreach ($items as $value => $text)
  780.         {
  781.             $_value 'value="'.htmlentities($valueENT_COMPAT$encodingfalse).'"';
  782.             if (in_array($value$selected))
  783.             {
  784.                 $_value .= ' selected="selected"';
  785.             }
  786.             $ret["<option {$_value}>{$text}</option>\n";
  787.         }
  788.         $ret['</select>';
  789.         return join(' '$ret);
  790.     }
  791.  
  792.     public function __set($name$value)
  793.     {
  794.         $this->data[$name$value;
  795.     }
  796.  
  797.     public function __get($name)
  798.     {
  799.         if (isset($this->data[$name]))
  800.         {
  801.             return $this->data[$name];
  802.         }
  803.         return null;
  804.     }
  805.     
  806.     /**
  807.      * Utility function taken from MYSQL
  808.      */
  809.     public static function coalesce()
  810.     {
  811.         $args func_get_args();
  812.         foreach ($args as &$arg)
  813.         {
  814.             if (isset($arg&& !empty($arg)) return $arg;
  815.         }
  816.         return null;
  817.     }
  818. }
  819.  
  820. /**
  821.  * Standard response for the json parser
  822.  * @package phery_package
  823.  * @subpackage phery_response
  824.  * @method phery_response detach() detach() Detach a DOM element retaining the events attached to it
  825.  * @method phery_response prependTo() pretendTo($target) Prepend DOM element to target
  826.  * @method phery_response appendTo() appendTo($target) Append DOM element to target
  827.  * @method phery_response replaceWith() replaceWith($newContent) The content to insert. May be an HTML string, DOM element, or jQuery object.
  828.  * @method phery_response css() css($propertyName, $value) propertyName: A CSS property name. value: A value to set for the property.
  829.  * @method phery_response toggle() toggle($speed) Toggle an object visible or hidden, can be animated with 'fast','slow','normal'
  830.  * @method phery_response hide() hide($speed) Hide an object, can be animated with 'fast','slow','normal'
  831.  * @method phery_response show() show($speed) Show an object, can be animated with 'fast','slow','normal'
  832.  * @method phery_response toggleClass() toggleClass($className) Add/Remove a class from an element
  833.  * @method phery_response data() data($name, $data) Add data to element
  834.  * @method phery_response addClass() addClass($className) Add a class from an element
  835.  * @method phery_response removeClass() removeClass($className) Remove a class from an element
  836.  * @method phery_response animate() animate($prop, $dur, $easing, $cb) Animate an element
  837.  * @method phery_response trigger() trigger($eventName, [$args]) Trigger an event
  838.  * @method phery_response fadeIn() fadeIn($prop, $dur, $easing, $cb) Animate an element
  839.  * @method phery_response filter() filter($selector) Filter elements
  840.  * @method phery_response fadeTo() fadeTo($dur, $opacity) Animate an element
  841.  * @method phery_response fadeOut() fadeOut($prop, $dur, $easing, $cb) Animate an element
  842.  * @method phery_response slideUp() slideUp($dur, $cb) Hide with slide up animation
  843.  * @method phery_response slideDown() slideDown($dur, $cb) Show with slide down animation
  844.  * @method phery_response slideToggle() slideToggle($dur, $cb) Toggle show/hide the element, using slide animation
  845.  * @method phery_response unbind() unbind($name) Unbind an event from an element
  846.  * @method phery_response stop() stop() Stop animation on elements
  847.  * @method phery_response live() live($name) Bind a live event to the selected elements
  848.  * @method phery_response die() die($name) Unbind an event from an element set by live()
  849.  * @method phery_response val() val($content) Set the value of an element
  850.  * @method phery_response removeData() removeData($element, $name) Remove element data added with data()
  851.  * @method phery_response removeAttr() removeAttr($name) Remove an attribute from an element
  852.  * @method phery_response scrollTop() scrollTop($val) Set the scroll from the top
  853.  * @method phery_response scrollLeft() scrollLeft($val) Set the scroll from the left
  854.  * @method phery_response height() height($val) Set the height from the left
  855.  * @method phery_response width() width($val) Set the width from the left
  856.  * @method phery_response slice() slice($start, $end) Reduce the set of matched elements to a subset specified by a range of indices.
  857.  * @method phery_response not() not($val) Remove elements from the set of matched elements.
  858.  * @method phery_response eq() eq($selector) Reduce the set of matched elements to the one at the specified index.
  859.  */
  860. class phery_response {
  861.  
  862.     /**
  863.      * Last jQuery selector defined
  864.      * @var string 
  865.      */
  866.     public $last_selector = null;
  867.     /**
  868.      * Array containing answer data
  869.      * @var array 
  870.      */
  871.     private $data array();
  872.     private $arguments array();
  873.  
  874.     /**
  875.      * @param string $selector Create the object already selecting the DOM element
  876.      */
  877.     function __construct($selector null)
  878.     {
  879.         $this->last_selector = $selector;
  880.     }
  881.  
  882.     /**
  883.      * Create a new phery_response instance for chaining, for one liners
  884.      * <code>
  885.      * function answer()
  886.      * {
  887.      *  return phery_response::factory('a#link')->attr('href', '#')->alert('done');
  888.      * }
  889.      * </code>
  890.      * @param string $selector 
  891.      * @static
  892.      * @return phery_response 
  893.      */
  894.     static function factory($selector null)
  895.     {
  896.         return new phery_response($selector);
  897.     }
  898.  
  899.     /**
  900.      * Merge another response to this one.
  901.      * Selectors with the same name will be added in order, for example:
  902.      * <code>
  903.      * function process()
  904.      * {
  905.      *     $response->jquery('a.links')->remove(); //from $response
  906.      *     // will execute before
  907.      *  // there will be no more "a.links", so the addClass() will fail silently
  908.      *     $response2->jquery('a.links')->addClass('red');
  909.      *     return $response->merge($response2);
  910.      * }
  911.      * </code>
  912.      * @param phery_response $phery Another phery_response object
  913.      * @return phery_response 
  914.      */
  915.     function merge(phery_response $phery)
  916.     {
  917.         $this->data array_merge_recursive($this->data$phery->data);
  918.         return $this;
  919.     }
  920.  
  921.     /**
  922.      * Sets the selector, so you can chain many calls to it
  923.      * @param string $selector Sets the current selector for subsequent chaining
  924.      *  <code>
  925.      *  $phery_response
  926.      *  ->jquery('.slides')
  927.      *  ->fadeTo(0,0)
  928.      *  ->css(array('top' => '10px', 'left' => '90px'));
  929.      *  </code>
  930.      * @return phery_response 
  931.      */
  932.     function jquery($selector)
  933.     {
  934.         $this->last_selector = $selector;
  935.         return $this;
  936.     }
  937.  
  938.     /**
  939.      * Shortcut/alias for jquery($selector)
  940.      * @param string $selector Sets the current selector for subsequent chaining
  941.      * @return phery_response 
  942.      */
  943.     function j($selector)
  944.     {
  945.         return $this->jquery($selector);
  946.     }
  947.  
  948.     /**
  949.      * Show an alert box
  950.      * @param string $msg Message to be displayed
  951.      * @return phery_response 
  952.      */
  953.     function alert($msg)
  954.     {
  955.         $this->last_selector = null;
  956.         $this->cmd(1array(
  957.             $msg
  958.         ));
  959.         return $this;
  960.     }
  961.  
  962.     /**
  963.      * Remove the current jQuery selector
  964.      * @param string $selector Set a selector
  965.      * @return phery_response 
  966.      */
  967.     function remove($selector null)
  968.     {
  969.         $this->cmd(0xffarray(
  970.             'remove'
  971.             ),
  972.             $selector);
  973.         return $this;
  974.     }
  975.  
  976.     /**
  977.      * Add a command to the response
  978.      * @param int $cmd Integer for command, see phery.js for more info
  979.      * @param array $args Array to pass to the response
  980.      * @param string $selector Insert the jquery selector
  981.      * @return phery_response 
  982.      */
  983.     function cmd($cmdarray $args$selector null)
  984.     {
  985.         $selector phery::coalesce($selector$this->last_selector);
  986.         if ($selector === null || !is_string($selector))
  987.         {
  988.             $this->data[=
  989.                 array(
  990.                     'c' => $cmd,
  991.                     'a' => $args
  992.             );
  993.         }
  994.         else
  995.         {
  996.             if (!isset($this->data[$selector])) $this->data[$selectorarray();
  997.             $this->data[$selector][=
  998.                 array(
  999.                     'c' => $cmd,
  1000.                     'a' => $args
  1001.             );
  1002.         }
  1003.  
  1004.         return $this;
  1005.     }
  1006.  
  1007.     /**
  1008.      * Set the attribute of a jQuery selector
  1009.      * Example:<br>
  1010.      * <code>
  1011.      * $phery_response->attr('href', 'http://url.com', 'a#link-' . $args['id']);
  1012.      * </code>
  1013.      * @param string $attr HTML attribute of the item
  1014.      * @param string $selector [optional] Provide the jQuery selector directly
  1015.      * @return phery_response 
  1016.      */
  1017.     function attr($attr$data$selector null)
  1018.     {
  1019.         $this->cmd(0xffarray(
  1020.             'attr',
  1021.             $attr,
  1022.             $data
  1023.             ),
  1024.             $selector);
  1025.         return $this;
  1026.     }
  1027.  
  1028.     /**
  1029.      * Call a javascript function.
  1030.      * Warning: calling this function will reset the selector jQuery selector previously stated
  1031.      * @param string $func_name Function name
  1032.      * @param mixed $args,... Any additional arguments to pass to the function
  1033.      * @return phery_response 
  1034.      */
  1035.     function call()
  1036.     {
  1037.         $args func_get_args();
  1038.         $func_name array_shift($args);
  1039.         $this->last_selector null;
  1040.  
  1041.         $this->cmd(2array(
  1042.             $func_name,
  1043.             $args
  1044.         ));
  1045.         return $this;
  1046.     }
  1047.  
  1048.     /**
  1049.      * Clear the selected attribute.
  1050.      * Alias for attr('attrname', '')
  1051.      * @see attr()
  1052.      * @param string $attr Name of the attribute to clear, such as 'innerHTML', 'style', 'href', etc
  1053.      * @param string $selector [optional] Provide the jQuery selector directly
  1054.      * @return phery_response 
  1055.      */
  1056.     function clear($attr$selector null)
  1057.     {
  1058.         return $this->attr($attr''$selector);
  1059.     }
  1060.  
  1061.     /**
  1062.      * Set the HTML content of an element.
  1063.      * Automatically typecasted to string, so classes that
  1064.      * respond to __toString() will be converted automatically
  1065.      * @param string $content 
  1066.      * @param string $selector [optional] Provide the jQuery selector directly
  1067.      * @return phery_response 
  1068.      */
  1069.     function html($content$selector null)
  1070.     {
  1071.         $this->cmd(0xffarray(
  1072.             'html',
  1073.             "{$content}"
  1074.             ),
  1075.             $selector);
  1076.         return $this;
  1077.     }
  1078.  
  1079.     /**
  1080.      * Set the text of an element.
  1081.      * Automatically typecasted to string, so classes that
  1082.      * respond to __toString() will be converted automatically
  1083.      * @param string $content 
  1084.      * @param string $selector [optional] Provide the jQuery selector directly
  1085.      * @return phery_response 
  1086.      */
  1087.     function text($content$selector null)
  1088.     {
  1089.         $this->cmd(0xffarray(
  1090.             'text',
  1091.             "{$content}"
  1092.             ),
  1093.             $selector);
  1094.         return $this;
  1095.     }
  1096.  
  1097.     /**
  1098.      * Compile a script and call it on-the-fly.
  1099.      * There is a closure on the executed function, so
  1100.      * to reach out global variables, you need to use window.variable
  1101.      * Warning: calling this function will reset the selector jQuery selector previously stated
  1102.      * @param string|array$script Script content. If provided an array, it will be joined with ;\n
  1103.      *  <code>
  1104.      *  phery_response::factory()
  1105.      *  ->script(array("if (confirm('Are you really sure?')) $('*').remove()"));
  1106.      *  </code>
  1107.      * @return phery_response 
  1108.      */
  1109.     function script($script)
  1110.     {
  1111.         $this->last_selector null;
  1112.  
  1113.         if (is_array($script))
  1114.         {
  1115.             $script join(";\n"$script);
  1116.         }
  1117.  
  1118.         $this->cmd(3array(
  1119.             $script
  1120.         ));
  1121.         return $this;
  1122.     }
  1123.  
  1124.     /**
  1125.      * Creates a redirect
  1126.      * @param string $url Complete url with http:// (according W3C http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30)
  1127.      * @return phery_response 
  1128.      */
  1129.     function redirect($url)
  1130.     {
  1131.         $this->script('window.location.href="'.htmlentities($url).'"');
  1132.         return $this;
  1133.     }
  1134.  
  1135.     /**
  1136.      * Prepend string/HTML to target(s)
  1137.      * @param string $content Content to be prepended to the selected element
  1138.      * @param string $selector [optional] Optional jquery selector string
  1139.      * @return phery_response 
  1140.      */
  1141.     function prepend($content$selector null)
  1142.     {
  1143.         $this->cmd(0xffarray(
  1144.             'prepend',
  1145.             $content
  1146.             ),
  1147.             $selector);
  1148.         return $this;
  1149.     }
  1150.  
  1151.     /**
  1152.      * Append string/HTML to target(s)
  1153.      * @param string $content Content to be appended to the selected element
  1154.      * @param string $selector [optional] Optional jquery selector string
  1155.      * @return phery_response 
  1156.      */
  1157.     function append($content$selector null)
  1158.     {
  1159.         $this->cmd(0xffarray(
  1160.             'append',
  1161.             $content
  1162.             ),
  1163.             $selector);
  1164.         return $this;
  1165.     }
  1166.  
  1167.     /**
  1168.      * Magically map to any additional jQuery function.
  1169.      * To reach this magically called functions, the jquery() selector must be called prior
  1170.      * to any jquery specific call
  1171.      * @see jquery()
  1172.      * @return phery_response 
  1173.      */
  1174.     function __call($name$arguments)
  1175.     {
  1176.         if (count($arguments))
  1177.         {
  1178.             foreach ($arguments as &$argument)
  1179.             {
  1180.                 if (ctype_digit($argument))
  1181.                 {
  1182.                     $argument = (int) $argument;
  1183.                 }
  1184.             }
  1185.         }
  1186.  
  1187.         $this->cmd(0xffarray(
  1188.             $name,
  1189.             $arguments
  1190.         ));
  1191.  
  1192.         return $this;
  1193.     }
  1194.  
  1195.     /**
  1196.      * Magic function to set data to the response before processing
  1197.      */
  1198.     public function __set($name$value)
  1199.     {
  1200.         $this->arguments[$name$value;
  1201.     }
  1202.  
  1203.     /**
  1204.      * Magic function to get data appended to the response object
  1205.      */
  1206.     public function __get($name)
  1207.     {
  1208.         if (isset($this->arguments[$name]))
  1209.         {
  1210.             return $this->arguments[$name];
  1211.         }
  1212.         else
  1213.         {
  1214.             return null;
  1215.         }
  1216.     }
  1217.     
  1218.     /**
  1219.      * Return the JSON encoded data
  1220.      * @return string 
  1221.      */
  1222.     function render()
  1223.     {
  1224.         return json_encode((object) $this->data);
  1225.     }
  1226.     
  1227.     /**
  1228.      * Return the JSON encoded data
  1229.      * if the object is typecasted as a string
  1230.      * @return string 
  1231.      */
  1232.     function __toString()
  1233.     {
  1234.         return $this->render();
  1235.     }
  1236.  
  1237. }
  1238.  
  1239. /**
  1240.  * Interface for CustomException
  1241.  * @package phery_package
  1242.  */
  1243. interface IException {
  1244.  
  1245.     /**
  1246.      *  Protected methods inherited from Exception class
  1247.      */
  1248.  
  1249.     /**
  1250.      * Exception message
  1251.      */
  1252.     public function getMessage();                 
  1253.     /**
  1254.      * User-defined Exception code
  1255.      */
  1256.     public function getCode();                    
  1257.     /**
  1258.      * Source filename
  1259.      */
  1260.     public function getFile();                    
  1261.     /**
  1262.      * Source line
  1263.      */
  1264.     public function getLine();                    
  1265.     /**
  1266.      * An array of the backtrace()
  1267.      */
  1268.     public function getTrace();                    
  1269.     /**
  1270.      * Formated string of trace
  1271.      */
  1272.     public function getTraceAsString();            
  1273.  
  1274.     /**
  1275.      *  Overrideable methods inherited from Exception class
  1276.      */
  1277.     /**
  1278.      * Formated string for display
  1279.      */
  1280.     public function __toString();
  1281.     /**
  1282.      * @param string $message 
  1283.      * @param int $code 
  1284.      */             
  1285.     public function __construct($message null$code 0);
  1286. }
  1287.  
  1288. /**
  1289.  * CustomException for phery
  1290.  * @package phery_package
  1291.  */
  1292. abstract class CustomException extends Exception implements IException {
  1293.  
  1294.     protected $message = 'Unknown exception';     // Exception message
  1295.     private $string;                            // Unknown
  1296.     protected $code = 0;                        // User-defined exception code
  1297.     protected $file;                             // Source filename of exception
  1298.     protected $line;                             // Source line of exception
  1299.     private $trace;                             // Unknown
  1300.  
  1301.     public function __construct($message null$code 0)
  1302.     {
  1303.         if (!$message)
  1304.         {
  1305.             throw new $this('Unknown '.get_class($this));
  1306.         }
  1307.         parent::__construct($message$code);
  1308.     }
  1309.  
  1310.     public function __toString()
  1311.     {
  1312.         return get_class($this)." '{$this->message}' in {$this->file}({$this->line})\n"
  1313.         ."{$this->getTraceAsString()}";
  1314.     }
  1315.  
  1316. }
  1317.  
  1318. /**
  1319.  * Exception class for phery specific exceptions
  1320.  * @package phery_package
  1321.  * @subpackage phery_exception
  1322.  */
  1323.  
  1324. class phery_exception extends CustomException {
  1325.  

Documentation generated on Tue, 14 Jun 2011 15:54:43 -0300 by phpDocumentor 1.4.3