Merge pull request #2771 from aternatik/api_rest

Add new core module : REST API
This commit is contained in:
Laurent Destailleur 2015-05-12 19:39:37 +02:00
commit cb6f23d9b6
107 changed files with 21493 additions and 171 deletions

View File

@ -13,7 +13,8 @@
"require": {
"php": ">=5.3.0",
"ext-gd": "*",
"ext-curl": "*"
"ext-curl": "*",
"restler/framework": "3.0.*"
},
"suggest": {
"ext-mysqli": "*",

123
dev/skeletons/build_api_class.php Executable file
View File

@ -0,0 +1,123 @@
#!/usr/bin/php
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file dev/skeletons/build_api_class.php
* \ingroup core
* \brief Create a complete API class file from existant class file
*/
$sapi_type = php_sapi_name();
$script_file = basename(__FILE__);
$path=dirname(__FILE__).'/';
// Test if batch mode
if (substr($sapi_type, 0, 3) == 'cgi') {
echo "Error: You are using PHP for CGI. To execute ".$script_file." from command line, you must use PHP for CLI mode.\n";
exit;
}
// Include Dolibarr environment
require_once($path."../../htdocs/master.inc.php");
// After this $db is a defined handler to database.
// Main
$version='1';
@set_time_limit(0);
$error=0;
$langs->load("main");
print "***** $script_file ($version) *****\n";
// -------------------- START OF BUILD_API_FROM_CLASS --------------------
// Check parameters
if (! isset($argv[1]) && ! isset($argv[2]))
{
print "Usage: $script_file phpClassFile phpClassName\n";
exit;
}
// Show parameters
print 'Classfile='.$argv[1]."\n";
print 'Classname='.$argv[2]."\n";
$classfile=$argv[1];
$classname=$argv[2];
$classmin=strtolower($classname);
$classnameApi = $classname.'Api';
$property=array();
$targetcontent='';
// Load the class and read properties
require_once($classfile);
$property=array();
$class = new $classname($db);
$values=get_class_vars($classname);
unset($values['db']);
unset($values['error']);
unset($values['errors']);
unset($values['element']);
unset($values['table_element']);
unset($values['table_element_line']);
unset($values['fk_element']);
unset($values['ismultientitymanaged']);
// Read skeleton_api_class.class.php file
$skeletonfile=$path.'skeleton_api_class.class.php';
$sourcecontent=file_get_contents($skeletonfile);
if (! $sourcecontent)
{
print "\n";
print "Error: Failed to read skeleton sample '".$skeletonfile."'\n";
print "Try to run script from skeletons directory.\n";
exit;
}
// Define output variables
$outfile='out.api_'.$classmin.'.class.php';
$targetcontent=$sourcecontent;
// Substitute class name
$targetcontent=preg_replace('/skeleton_api_class\.class\.php/', 'api_'.$classmin.'.class.php', $targetcontent);
$targetcontent=preg_replace('/skeleton/', $classmin, $targetcontent);
//$targetcontent=preg_replace('/\$table_element=\'skeleton\'/', '\$table_element=\''.$tablenoprefix.'\'', $targetcontent);
$targetcontent=preg_replace('/SkeletonApi/', $classnameApi, $targetcontent);
$targetcontent=preg_replace('/Skeleton/', $classname, $targetcontent);
// Build file
$fp=fopen($outfile,"w");
if ($fp)
{
fputs($fp, $targetcontent);
fclose($fp);
print "\n";
print "File '".$outfile."' has been built in current directory.\n";
}
else $error++;
// -------------------- END OF BUILD_CLASS_FROM_TABLE SCRIPT --------------------
print "You can now rename generated files by removing the 'out.' prefix in their name and store them into directory /module/class.\n";
return $error;

View File

@ -0,0 +1,288 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\RestException;
/**
* API class for skeleton object
*
* @smart-auto-routing false
* @access protected
* @class DolibarrApiAccess {@requires user,external}
*
*
*/
class SkeletonApi extends DolibarrApi
{
/**
* @var array $FIELDS Mandatory fields, checked when create and update object
*/
static $FIELDS = array(
'name'
);
/**
* @var Skeleton $skeleton {@type Skeleton}
*/
public $skeleton;
/**
* Constructor
*
* @url GET skeleton/
*
*/
function __construct()
{
global $db, $conf;
$this->db = $db;
$this->skeleton = new Skeleton($this->db);
}
/**
* Get properties of a skeleton object
*
* Return an array with skeleton informations
*
* @param int $id ID of skeleton
* @return array|mixed data without useless information
*
* @url GET skeleton/{id}
* @throws RestException
*/
function get($id)
{
if(! DolibarrApiAccess::$user->rights->skeleton->read) {
throw new RestException(401);
}
$result = $this->skeleton->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Skeleton not found');
}
if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
return $this->_cleanObjectDatas($this->skeleton);
}
/**
* List skeletons
*
* Get a list of skeletons
*
* @param int $mode Use this param to filter list
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return array Array of skeleton objects
*
* @url GET /skeletons/
*/
function getList($mode, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
global $db, $conf;
$obj_ret = array();
$socid = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : '';
// If the internal user must only see his customers, force searching by him
if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) $search_sale = DolibarrApiAccess::$user->id;
$sql = "SELECT s.rowid";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
$sql.= " FROM ".MAIN_DB_PREFIX."skeleton as s";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
$sql.= ", ".MAIN_DB_PREFIX."c_stcomm as st";
$sql.= " WHERE s.fk_stcomm = st.id";
// Example of use $mode
//if ($mode == 1) $sql.= " AND s.client IN (1, 3)";
//if ($mode == 2) $sql.= " AND s.client IN (2, 3)";
$sql.= ' AND s.entity IN ('.getEntity('skeleton', 1).')';
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= " AND s.fk_soc = sc.fk_soc";
if ($socid) $sql.= " AND s.fk_soc = ".$socid;
if ($search_sale > 0) $sql.= " AND s.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
// Insert sale filter
if ($search_sale > 0)
{
$sql .= " AND sc.fk_user = ".$search_sale;
}
$nbtotalofrecords = 0;
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
{
$result = $db->query($sql);
$nbtotalofrecords = $db->num_rows($result);
}
$sql.= $db->order($sortfield, $sortorder);
if ($limit) {
if ($page < 0)
{
$page = 0;
}
$offset = $limit * $page;
$sql.= $db->plimit($limit + 1, $offset);
}
$result = $db->query($sql);
if ($result)
{
$num = $db->num_rows($result);
while ($i < $num)
{
$obj = $db->fetch_object($result);
$skeleton_static = new Skeleton($db);
if($skeleton_static->fetch($obj->rowid)) {
$obj_ret[] = parent::_cleanObjectDatas($skeleton_static);
}
$i++;
}
}
else {
throw new RestException(503, 'Error when retrieve skeleton list');
}
if( ! count($obj_ret)) {
throw new RestException(404, 'No skeleton found');
}
return $obj_ret;
}
/**
* Create skeleton object
*
* @param array $request_data Request datas
* @return int ID of skeleton
*
* @url POST skeleton/
*/
function post($request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->skeleton->create) {
throw new RestException(401);
}
// Check mandatory fields
$result = $this->_validate($request_data);
foreach($request_data as $field => $value) {
$this->skeleton->$field = $value;
}
if( ! $this->skeleton->create(DolibarrApiAccess::$user)) {
throw new RestException(500);
}
return $this->skeleton->id;
}
/**
* Update skeleton
*
* @param int $id Id of skeleton to update
* @param array $request_data Datas
* @return int
*
* @url PUT skeleton/{id}
*/
function put($id, $request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->skeleton->create) {
throw new RestException(401);
}
$result = $this->skeleton->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Skeleton not found');
}
if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
foreach($request_data as $field => $value) {
$this->skeleton->$field = $value;
}
if($this->skeleton->update($id, DolibarrApiAccess::$user))
return $this->get ($id);
return false;
}
/**
* Delete skeleton
*
* @param int $id Skeleton ID
* @return array
*
* @url DELETE skeleton/{id}
*/
function delete($id)
{
if(! DolibarrApiAccess::$user->rights->skeleton->supprimer) {
throw new RestException(401);
}
$result = $this->skeleton->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Skeleton not found');
}
if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
if( !$this->skeleton->delete($id))
{
throw new RestException(500);
}
return array(
'success' => array(
'code' => 200,
'message' => 'Skeleton deleted'
)
);
}
/**
* Validate fields before create or update object
*
* @param array $data Data to validate
* @return array
*
* @throws RestException
*/
function _validate($data)
{
$skeleton = array();
foreach (SkeletonApi::$FIELDS as $field) {
if (!isset($data[$field]))
throw new RestException(400, "$field field missing");
$skeleton[$field] = $data[$field];
}
return $skeleton;
}
}

138
htdocs/api/admin/api.php Normal file
View File

@ -0,0 +1,138 @@
<?php
/* Copyright (C) 2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2005-2010 Laurent Destailleur <eldy@users.sourceforge.org>
* Copyright (C) 2011 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2012 Regis Houssin <regis.houssin@capnetworks.com>
* Copyright (C) 2015 Regis Houssin <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/api/admin/api.php
* \ingroup api
* \brief Page to setup api module
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
$langs->load("admin");
if (! $user->admin)
accessforbidden();
$actionsave=GETPOST("save");
// Sauvegardes parametres
if ($actionsave)
{
$i=0;
$db->begin();
$i+=dolibarr_set_const($db,'API_KEY',trim(GETPOST("API_KEY")),'chaine',0,'',$conf->entity);
if ($i >= 1)
{
$db->commit();
setEventMessage($langs->trans("SetupSaved"));
}
else
{
$db->rollback();
setEventMessage($langs->trans("Error"), 'errors');
}
}
/*
* View
*/
llxHeader();
$linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
print_fiche_titre($langs->trans("ApiSetup"),$linkback,'title_setup');
print $langs->trans("ApiDesc")."<br>\n";
print "<br>\n";
print '<form name="apisetupform" action="'.$_SERVER["PHP_SELF"].'" method="post">';
print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
print '<table class="noborder" width="100%">';
print '<tr class="liste_titre">';
print "<td>".$langs->trans("Parameter")."</td>";
print "<td>".$langs->trans("Value")."</td>";
print "<td>&nbsp;</td>";
print "</tr>";
print '<tr class="impair">';
print '<td class="fieldrequired">'.$langs->trans("KeyForApiAccess").'</td>';
print '<td><input type="text" class="flat" id="API_KEY" name="API_KEY" value="'. (GETPOST('API_KEY')?GETPOST('API_KEY'):(! empty($conf->global->API_KEY)?$conf->global->API_KEY:'')) . '" size="40">';
if (! empty($conf->use_javascript_ajax))
print '&nbsp;'.img_picto($langs->trans('Generate'), 'refresh', 'id="generate_token" class="linkobject"');
print '</td>';
print '<td>&nbsp;</td>';
print '</tr>';
print '</table>';
print '<br><div class="center">';
print '<input type="submit" name="save" class="button" value="'.$langs->trans("Save").'">';
print '</div>';
print '</form>';
print '<br><br>';
// API endpoint
print '<u>'.$langs->trans("ApiEndPointIs").':</u><br>';
$url=DOL_MAIN_URL_ROOT.'/public/api/';
print img_picto('','object_globe.png').' <a href="'.$url.'" target="_blank">'.$url."</a><br>\n";
$url=DOL_MAIN_URL_ROOT.'/public/api/.json';
print img_picto('','object_globe.png').' <a href="'.$url.'" target="_blank">'.$url."</a><br>\n";
// Explorer
print '<u>'.$langs->trans("ApiExporerIs").':</u><br>';
$url=DOL_MAIN_URL_ROOT.'/public/api/explorer/index.html';
print img_picto('','object_globe.png').' <a href="'.$url.'" target="_blank">'.$url."</a><br>\n";
print '<br>';
print '<br>';
print $langs->trans("OnlyActiveElementsAreExposed", DOL_URL_ROOT.'/admin/modules.php');
if (! empty($conf->use_javascript_ajax))
{
print "\n".'<script type="text/javascript">';
print '$(document).ready(function () {
$("#generate_token").click(function() {
$.get( "'.DOL_URL_ROOT.'/core/ajax/security.php", {
action: \'getrandompassword\',
generic: true
},
function(token) {
$("#API_KEY").val(token);
});
});
});';
print '</script>';
}
llxFooter();
$db->close();

View File

@ -0,0 +1,215 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\Restler;
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
/**
* Class for API
*
*/
class DolibarrApi
{
/**
* @var DoliDb $db Database object
*/
static protected $db;
/**
* @var Restler $r Restler object
*/
var $r;
/**
* Constructor
*
* @param DoliDb $db Database handler
*/
function __construct($db) {
$this->db = $db;
$this->r = new Restler();
}
/**
* Executed method when API is called without parameter
*
* Display a short message an return a http code 200
*
* @return array
*/
function index()
{
return array(
'success' => array(
'code' => 200,
'message' => __class__.' is up and running!'
)
);
}
/**
* Clean sensible object datas
*
* @param object $object Object to clean
* @return array Array of cleaned object properties
*
* @todo use an array for properties to clean
*
*/
function _cleanObjectDatas($object) {
// Remove $db object property for object
unset($object->db);
// If object has lines, remove $db property
if(isset($object->lines) && count($object->lines) > 0) {
for($i=0; $i < count($object->lines); $i++) {
$this->_cleanObjectDatas($object->lines[$i]);
}
}
// If object has linked objects, remove $db property
if(isset($object->linkedObjects) && count($object->linkedObjects) > 0) {
foreach($object->linkedObjects as $type_object => $linked_object) {
foreach($linked_object as $object2clean) {
$this->_cleanObjectDatas($object2clean);
}
}
}
return $object;
}
/**
* Check user access to a resource
*
* Check access by user to a given resource
*
* @param string $resource element to check
* @param int $resource_id Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional).
* @param type $dbtablename 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity. Not used if objectid is null (optional)
* @param string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'.
* @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional)
* @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional)
* @throws RestException
*/
static function _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid') {
// Features/modules to check
$featuresarray = array($resource);
if (preg_match('/&/', $resource)) {
$featuresarray = explode("&", $resource);
}
else if (preg_match('/\|/', $resource)) {
$featuresarray = explode("|", $resource);
}
// More subfeatures to check
if (! empty($feature2)) {
$feature2 = explode("|", $feature2);
}
return checkUserAccessToObject(DolibarrApiAccess::$user, $featuresarray,$resource_id,$dbtablename,$feature2,$dbt_keyfield,$dbt_select);
}
}
/**
* API init
*
*/
class DolibarrApiInit extends DolibarrApi
{
function __construct() {
global $db;
$this->db = $db;
}
/**
* Login
*
* Log user with username and password
*
* @param string $login Username
* @param string $password User password
* @param int $entity User entity
* @return array Response status and user token
*
* @throws RestException
*/
public function login($login, $password, $entity = 0) {
// Authentication mode
if (empty($dolibarr_main_authentication))
$dolibarr_main_authentication = 'http,dolibarr';
// Authentication mode: forceuser
if ($dolibarr_main_authentication == 'forceuser' && empty($dolibarr_auto_user))
$dolibarr_auto_user = 'auto';
// Set authmode
$authmode = explode(',', $dolibarr_main_authentication);
include_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
$login = checkLoginPassEntity($login, $password, $entity, $authmode);
if (empty($login))
{
throw new RestException(403, 'Access denied');
}
// Generate token for user
$token = dol_hash($login.uniqid().$conf->global->MAIN_API_KEY,1);
// We store API token into database
$sql = "UPDATE ".MAIN_DB_PREFIX."user";
$sql.= " SET api_key = '".$this->db->escape($token)."'";
$sql.= " WHERE login = '".$this->db->escape($login)."'";
dol_syslog(get_class($this)."::login", LOG_DEBUG); // No log
$result = $this->db->query($sql);
if (!$result)
{
throw new RestException(500, 'Error when updating user :'.$this->db->error_msg);
}
//return token
return array(
'success' => array(
'code' => 200,
'token' => $token,
'message' => 'Welcome ' . $login
)
);
}
/**
* Get status (Dolibarr version)
*
* @access protected
* @class DolibarrApiAccess {@requires admin}
*/
function status() {
require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
return array(
'success' => array(
'code' => 200,
'dolibarr_version' => DOL_VERSION
)
);
}
}

View File

@ -0,0 +1,144 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use \Luracast\Restler\iAuthenticate;
use \Luracast\Restler\Resources;
use \Luracast\Restler\Defaults;
use Luracast\Restler\RestException;
/**
* Dolibarr API access class
*
*/
class DolibarrApiAccess implements iAuthenticate
{
const REALM = 'Restricted Dolibarr API';
/**
* @var array $requires role required by API method user / external / admin
*/
public static $requires = array('user','external','admin');
/**
* @var string $role user role
*/
public static $role = 'user';
/**
* @var User $user Loggued user
*/
public static $user = '';
// @codingStandardsIgnoreStart
/**
* @return string string to be used with WWW-Authenticate header
* @example Basic
* @example Digest
* @example OAuth
*/
public function __getWWWAuthenticateString();
/**
* Check access
*
* @return boolean
*/
public function _isAllowed()
{
// @codingStandardsIgnoreEnd
global $db;
$stored_key = '';
$userClass = Defaults::$userIdentifierClass;
if (isset($_GET['api_key'])) {
$sql = "SELECT u.login, u.datec, u.api_key, ";
$sql.= " u.tms as date_modification, u.entity";
$sql.= " FROM ".MAIN_DB_PREFIX."user as u";
$sql.= " WHERE u.api_key = '".$db->escape($_GET['api_key'])."'";
if ($db->query($sql))
{
if ($db->num_rows($result))
{
$obj = $db->fetch_object($result);
$login = $obj->login;
$stored_key = $obj->api_key;
}
}
else {
throw new RestException(503, 'Error when fetching user api_key :'.$db->error_msg);
}
if ( $stored_key != $_GET['api_key']) {
$userClass::setCacheIdentifier($_GET['api_key']);
return false;
}
$fuser = new User($db);
if(! $fuser->fetch('',$login)) {
throw new RestException(503, 'Error when fetching user :'.$fuser->error);
}
$fuser->getrights();
static::$user = $fuser;
if($fuser->societe_id)
static::$role = 'external';
if($fuser->admin)
static::$role = 'admin';
}
else
{
return false;
}
$userClass::setCacheIdentifier(static::$role);
Resources::$accessControlFunction = 'DolibarrApiAccess::verifyAccess';
return in_array(static::$role, (array) static::$requires) || static::$role == 'admin';
}
// @codingStandardsIgnoreStart
public function __getWWWAuthenticateString()
{
return '';
}
// @codingStandardsIgnoreEnd
/**
* Verify access
*
* @param array $m Properties of method
*
* @access private
*/
public static function verifyAccess(array $m)
{
$requires = isset($m['class']['DolibarrApiAccess']['properties']['requires'])
? $m['class']['DolibarrApiAccess']['properties']['requires']
: false;
return $requires
? static::$role == 'admin' || in_array(static::$role, (array) $requires)
: true;
}
}

8
htdocs/api/index.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/

50
htdocs/api/readme.md Normal file
View File

@ -0,0 +1,50 @@
API howto
=========
Explore the api
---------------
You can explore API method by using web interface : https://**yourdolibarr.tld**/htdocs/public/api/explorer/index.html (replace **yourdolibarr.tld** by real hostname of your Dolibarr installation)
Access to the API
---------------
> **Warning : access to the API should (or better : must!) be secured with SSL connection**
To access to the API you need a token to identify. When you access the API for the first time, you need to log in with user name and password to get a token. **Only** this token will allow to access API with.
To log in with the API, use this uri : https://**yourdolibarr.tld**/htdocs/public/api/login?login=**username**&password=**password** (replace bold strings with real values)
The token will be saved by Dolibarr for next user accesses to the API and it **must** be put into request uri as **api_key** parameter.
Develop the API
--------------
The API uses Lucarast Restler framework. Please check documentation https://www.luracast.com/products/restler and examples http://help.luracast.com/restler/examples/
Github contains also usefull informations : https://github.com/Luracast/Restler
To implement it into Dolibarr, we need to create a specific class for object we want to use. A skeleton file is available into /dev directory : *skeleton_api_class.class.php*
The API class file must be put into object class directory, with specific file name. By example, API class file for '*myobject*' must be put as : /htdocs/*myobject*/class/api_*myobject*.class.php. Class must be named **MyobjectApi**.
If a module provide several object, use a different name for '*myobject*' and put the file into the same directory.
**Define url for methods**
It is possible to specify url for API methods by simply use the PHPDoc tag **@url**. See examples :
/**
* List contacts
*
* Get a list of contacts
*
* @url GET /contact/list
* @url GET /contact/list/{socid}
* @url GET /thirdparty/{socid}/contacts
* [...]
**Other Annotations**
Other annotations are used, you are encouraged to read them : https://github.com/Luracast/Restler/blob/master/ANNOTATIONS.md
PHPDoc tags can also be used to specify variables informations for API. Again, rtfm : https://github.com/Luracast/Restler/blob/master/PARAM.md

View File

@ -0,0 +1,355 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
/**
* API class for category object
*
* @smart-auto-routing false
* @access protected
* @class DolibarrApiAccess {@requires user,external}
*
*
*/
class CategoryApi extends DolibarrApi
{
/**
* @var array $FIELDS Mandatory fields, checked when create and update object
*/
static $FIELDS = array(
'label',
'type'
);
static $TYPES = array(
0 => 'product',
1 => 'supplier',
2 => 'customer',
3 => 'member',
4 => 'contact',
);
/**
* @var Categorie $category {@type Categorie}
*/
public $category;
/**
* Constructor
*
* @url GET category/
*
*/
function __construct()
{
global $db, $conf;
$this->db = $db;
$this->category = new Categorie($this->db);
}
/**
* Get properties of a category object
*
* Return an array with category informations
*
* @param int $id ID of category
* @return array|mixed data without useless information
*
* @url GET category/{id}
* @throws RestException
*/
function get($id)
{
if(! DolibarrApiAccess::$user->rights->categorie->lire) {
throw new RestException(401);
}
$result = $this->category->fetch($id);
if( ! $result ) {
throw new RestException(404, 'category not found');
}
if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
return $this->_cleanObjectDatas($this->category);
}
/**
* List categories
*
* Get a list of categories
*
* @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact')
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
* @return array Array of category objects
*
* @url GET /category/list
*/
function getList($type='product', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
global $db, $conf;
$obj_ret = array();
if(! DolibarrApiAccess::$user->rights->categorie->lire) {
throw new RestException(401);
}
$sql = "SELECT s.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX."categorie as s";
$sql.= ' WHERE s.entity IN ('.getEntity('categorie', 1).')';
$sql.= ' AND s.type='.array_search($type,CategoryApi::$TYPES);
$nbtotalofrecords = 0;
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
{
$result = $db->query($sql);
$nbtotalofrecords = $db->num_rows($result);
}
$sql.= $db->order($sortfield, $sortorder);
if ($limit) {
if ($page < 0)
{
$page = 0;
}
$offset = $limit * $page;
$sql.= $db->plimit($limit + 1, $offset);
}
$result = $db->query($sql);
if ($result)
{
$num = $db->num_rows($result);
while ($i < $num)
{
$obj = $db->fetch_object($result);
$category_static = new Categorie($db);
if($category_static->fetch($obj->rowid)) {
$obj_ret[] = parent::_cleanObjectDatas($category_static);
}
$i++;
}
}
else {
throw new RestException(503, 'Error when retrieve category list : '.$category_static->error);
}
if( ! count($obj_ret)) {
throw new RestException(404, 'No category found');
}
return $obj_ret;
}
/**
* Get member categories list
*
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
* @return mixed
*
* @url GET /category/list/member
*/
function getListCategoryMember($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
return $this->getList('member', $sortfield, $sortorder, $limit, $page);
}
/**
* Get customer categories list
*
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return mixed
*
* @url GET /category/list/customer
*/
function getListCategoryCustomer($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
return $this->getList('customer', $sortfield, $sortorder, $limit, $page);
}
/**
* Get supplier categories list
*
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return mixed
*
* @url GET /category/list/supplier
*/
function getListCategorySupplier($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
return $this->getList('supplier', $sortfield, $sortorder, $limit, $page);
}
/**
* Get product categories list
*
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return mixed
*
* @url GET /category/list/product
*/
function getListCategoryProduct($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
return $this->getList('product', $sortfield, $sortorder, $limit, $page);
}
/**
* Get contact categories list
*
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
* @return mixed
*
* @url GET /category/list/contact
*/
function getListCategoryContact($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
return $this->getList('contact', $sortfield, $sortorder, $limit, $page);
}
/**
* Create category object
*
* @param array $request_data Request data
* @return int ID of category
*
* @url POST category/
*/
function post($request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->categorie->creer) {
throw new RestException(401);
}
// Check mandatory fields
$result = $this->_validate($request_data);
foreach($request_data as $field => $value) {
$this->category->$field = $value;
}
if($this->category->create(DolibarrApiAccess::$user) < 0) {
throw new RestException(503, 'Error when create category : '.$this->category->error);
}
return $this->category->id;
}
/**
* Update category
*
* @param int $id Id of category to update
* @param array $request_data Datas
* @return int
*
* @url PUT category/{id}
*/
function put($id, $request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->categorie->creer) {
throw new RestException(401);
}
$result = $this->category->fetch($id);
if( ! $result ) {
throw new RestException(404, 'category not found');
}
if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
foreach($request_data as $field => $value) {
$this->category->$field = $value;
}
if($this->category->update(DolibarrApiAccess::$user))
return $this->get ($id);
return false;
}
/**
* Delete category
*
* @param int $id Category ID
* @return array
*
* @url DELETE category/{id}
*/
function delete($id)
{
if(! DolibarrApiAccess::$user->rights->categorie->supprimer) {
throw new RestException(401);
}
$result = $this->category->fetch($id);
if( ! $result ) {
throw new RestException(404, 'category not found');
}
if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
if (! $this->category->delete(DolibarrApiAccess::$user)) {
throw new RestException(401,'error when delete category');
}
return array(
'success' => array(
'code' => 200,
'message' => 'Category deleted'
)
);
}
/**
* Validate fields before create or update object
*
* @param array $data Data to validate
* @return array
*
* @throws RestException
*/
function _validate($data)
{
$category = array();
foreach (CategoryApi::$FIELDS as $field) {
if (!isset($data[$field]))
throw new RestException(400, "$field field missing");
$category[$field] = $data[$field];
}
return $category;
}
}

View File

@ -0,0 +1,335 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
/**
* API class for commande object
*
* @smart-auto-routing false
* @access protected
* @class DolibarrApiAccess {@requires user,external}
*
* @category Api
* @package Api
*
*
*/
class CommandeApi extends DolibarrApi
{
/**
* @var array $FIELDS Mandatory fields, checked when create and update object
*/
static $FIELDS = array(
'socid'
);
/**
* @var Commande $commande {@type Commande}
*/
public $commande;
/**
* Constructor
*
* @url GET order/
*
*/
function __construct()
{
global $db, $conf;
$this->db = $db;
$this->commande = new Commande($this->db);
}
/**
* Get properties of a commande object
*
* Return an array with commande informations
*
* @param int $id ID of order
* @param string $ref Ref of object
* @param string $ref_ext External reference of object
* @param string $ref_int Internal reference of other object
* @return array|mixed data without useless information
*
* @url GET order/{id}
* @throws RestException
*/
function get($id='',$ref='', $ref_ext='', $ref_int='')
{
if(! DolibarrApiAccess::$user->rights->commande->lire) {
throw new RestException(401);
}
$result = $this->commande->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Order not found');
}
if( ! DolibarrApi::_checkAccessToResource('commande',$this->commande->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$this->commande->fetchObjectLinked();
return $this->_cleanObjectDatas($this->commande);
}
/**
* List orders
*
* Get a list of orders
*
* @param int $mode Use this param to filter list
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @url GET /order/list
* @return array Array of order objects
*/
function getList($mode=0, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
global $db, $conf;
$obj_ret = array();
$socid = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : '';
// If the internal user must only see his customers, force searching by him
if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) $search_sale = DolibarrApiAccess::$user->id;
$sql = "SELECT s.rowid";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
$sql.= " FROM ".MAIN_DB_PREFIX."commande as s";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
// Example of use $mode
//if ($mode == 1) $sql.= " AND s.client IN (1, 3)";
//if ($mode == 2) $sql.= " AND s.client IN (2, 3)";
$sql.= ' WHERE s.entity IN ('.getEntity('commande', 1).')';
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= " AND s.fk_soc = sc.fk_soc";
if ($socid) $sql.= " AND s.fk_soc = ".$socid;
if ($search_sale > 0) $sql.= " AND s.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
// Insert sale filter
if ($search_sale > 0)
{
$sql .= " AND sc.fk_user = ".$search_sale;
}
$nbtotalofrecords = 0;
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
{
$result = $db->query($sql);
$nbtotalofrecords = $db->num_rows($result);
}
$sql.= $db->order($sortfield, $sortorder);
if ($limit) {
if ($page < 0)
{
$page = 0;
}
$offset = $limit * $page;
$sql.= $db->plimit($limit + 1, $offset);
}
$result = $db->query($sql);
if ($result)
{
$num = $db->num_rows($result);
while ($i < $num)
{
$obj = $db->fetch_object($result);
$commande_static = new Commande($db);
if($commande_static->fetch($obj->rowid)) {
$obj_ret[] = parent::_cleanObjectDatas($commande_static);
}
$i++;
}
}
else {
throw new RestException(503, 'Error when retrieve commande list');
}
if( ! count($obj_ret)) {
throw new RestException(404, 'No commande found');
}
return $obj_ret;
}
/**
* Create order object
*
* @param array $request_data Request datas
*
* @url POST order/
*
* @return int ID of commande
*/
function post($request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->commande->creer) {
throw new RestException(401);
}
// Check mandatory fields
$result = $this->_validate($request_data);
foreach($request_data as $field => $value) {
$this->commande->$field = $value;
}
if(! $this->commande->create(DolibarrApiAccess::$user) ) {
throw new RestException(401);
}
return $this->commande->ref;
}
/**
* Update order
*
* @param int $id Id of commande to update
* @param array $request_data Datas
*
* @url PUT order/{id}
*
* @return int
*/
function put($id, $request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->commande->creer) {
throw new RestException(401);
}
$result = $this->commande->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Commande not found');
}
if( ! DolibarrApi::_checkAccessToResource('commande',$this->commande->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
foreach($request_data as $field => $value) {
$this->commande->$field = $value;
}
if($this->commande->update($id, DolibarrApiAccess::$user,1,'','','update'))
return $this->get ($id);
return false;
}
/**
* Delete order
*
* @param int $id Order ID
*
* @url DELETE order/{id}
*
* @return array
*/
function delete($id)
{
if(! DolibarrApiAccess::$user->rights->commande->supprimer) {
throw new RestException(401);
}
$result = $this->commande->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Order not found');
}
if( ! DolibarrApi::_checkAccessToResource('commande',$this->commande->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
if( ! $this->commande->delete(DolibarrApiAccess::$user)) {
throw new RestException(500, 'Error when delete order : '.$this->commande->error);
}
return array(
'success' => array(
'code' => 200,
'message' => 'Order deleted'
)
);
}
/**
* Validate an order
*
* @param int $id Order ID
* @param int $idwarehouse Warehouse ID
*
* @url GET order/{id}/validate
* @url POST order/{id}/validate
*
* @return array
*
*/
function validOrder($id, $idwarehouse=0)
{
if(! DolibarrApiAccess::$user->rights->commande->creer) {
throw new RestException(401);
}
$result = $this->commande->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Order not found');
}
if( ! DolibarrApi::_checkAccessToResource('commande',$this->commande->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
if( ! $this->commande->valid(DolibarrApiAccess::$user, $idwarehouse)) {
throw new RestException(500, 'Error when validate order');
}
return array(
'success' => array(
'code' => 200,
'message' => 'Order validated'
)
);
}
/**
* Validate fields before create or update object
*
* @param array $data Array with data to verify
* @return array
* @throws RestException
*/
function _validate($data)
{
$commande = array();
foreach (CommandeApi::$FIELDS as $field) {
if (!isset($data[$field]))
throw new RestException(400, "$field field missing");
$commande[$field] = $data[$field];
}
return $commande;
}
}

View File

@ -0,0 +1,297 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
/**
* API class for invoice object
*
* @smart-auto-routing false
* @access protected
* @class DolibarrApiAccess {@requires user,external}
*
*/
class InvoiceApi extends DolibarrApi
{
/**
*
* @var array $FIELDS Mandatory fields, checked when create and update object
*/
static $FIELDS = array(
'socid'
);
/**
* @var Facture $invoice {@type Facture}
*/
public $invoice;
/**
* Constructor
*
* @url GET invoice/
*
*/
function __construct()
{
global $db, $conf;
$this->db = $db;
$this->invoice = new Facture($this->db);
}
/**
* Get properties of a invoice object
*
* Return an array with invoice informations
*
* @param int $id ID of invoice
* @return array|mixed data without useless information
*
* @url GET invoice/{id}
* @throws RestException
*/
function get($id)
{
if(! DolibarrApiAccess::$user->rights->facture->lire) {
throw new RestException(401);
}
$result = $this->invoice->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Facture not found');
}
if( ! DolibarrApi::_checkAccessToResource('facture',$this->invoice->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
return $this->_cleanObjectDatas($this->invoice);
}
/**
* List invoices
*
* Get a list of invoices
*
* @param int $socid Filter list with thirdparty ID
* @param string $mode Filter by invoice status : draft | unpaid | paid | cancelled
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return array Array of invoice objects
*
* @url GET invoice/list
* @url GET invoice/list/{mode}
* @url GET thirdparty/{socid}/invoice/list
* @url GET thirdparty/{socid}/invoice/list/{mode}
*/
function getList($socid=0, $mode='', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
global $db, $conf;
$obj_ret = array();
$socid = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : '';
// If the internal user must only see his customers, force searching by him
if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) $search_sale = DolibarrApiAccess::$user->id;
$sql = "SELECT s.rowid";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
$sql.= " FROM ".MAIN_DB_PREFIX."facture as s";
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
$sql.= ' WHERE s.entity IN ('.getEntity('facture', 1).')';
if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= " AND s.fk_soc = sc.fk_soc";
if ($socid) $sql.= " AND s.fk_soc = ".$socid;
if ($search_sale > 0) $sql.= " AND s.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
// Example of use $mode
if ($mode == 'draft') $sql.= " AND s.fk_statut IN (0)";
if ($mode == 'unpaid') $sql.= " AND s.fk_statut IN (1)";
if ($mode == 'paid') $sql.= " AND s.fk_statut IN (2)";
if ($mode == 'cancelled') $sql.= " AND s.fk_statut IN (3)";
// Insert sale filter
if ($search_sale > 0)
{
$sql .= " AND sc.fk_user = ".$search_sale;
}
$nbtotalofrecords = 0;
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
{
$result = $db->query($sql);
$nbtotalofrecords = $db->num_rows($result);
}
$sql.= $db->order($sortfield, $sortorder);
if ($limit) {
if ($page < 0)
{
$page = 0;
}
$offset = $limit * $page;
$sql.= $db->plimit($limit + 1, $offset);
}
$result = $db->query($sql);
if ($result)
{
$num = $db->num_rows($result);
while ($i < $num)
{
$obj = $db->fetch_object($result);
$invoice_static = new Facture($db);
if($invoice_static->fetch($obj->rowid)) {
$obj_ret[] = parent::_cleanObjectDatas($invoice_static);
}
$i++;
}
}
else {
throw new RestException(503, 'Error when retrieve invoice list');
}
if( ! count($obj_ret)) {
throw new RestException(404, 'No invoice found');
}
return $obj_ret;
}
/**
* Create invoice object
*
* @param array $request_data Request datas
* @return int ID of invoice
*
* @url POST invoice/
*/
function post($request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->facture->creer) {
throw new RestException(401);
}
// Check mandatory fields
$result = $this->_validate($request_data);
foreach($request_data as $field => $value) {
$this->invoice->$field = $value;
}
if(! array_keys($request_data,'date')) {
$this->invoice->date = dol_now();
}
if( ! $this->invoice->create(DolibarrApiAccess::$user)) {
throw new RestException(500);
}
return $this->invoice->id;
}
/**
* Update invoice
*
* @param int $id Id of invoice to update
* @param array $request_data Datas
* @return int
*
* @url PUT invoice/{id}
*/
function put($id, $request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->facture->creer) {
throw new RestException(401);
}
$result = $this->invoice->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Facture not found');
}
if( ! DolibarrApi::_checkAccessToResource('facture',$this->invoice->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
foreach($request_data as $field => $value) {
$this->invoice->$field = $value;
}
if($this->invoice->update($id, DolibarrApiAccess::$user))
return $this->get ($id);
return false;
}
/**
* Delete invoice
*
* @param int $id Invoice ID
* @return type
*
* @url DELETE invoice/{id}
*/
function delete($id)
{
if(! DolibarrApiAccess::$user->rights->facture->supprimer) {
throw new RestException(401);
}
$result = $this->invoice->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Facture not found');
}
if( ! DolibarrApi::_checkAccessToResource('facture',$this->facture->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
if( !$this->invoice->delete($id))
{
throw new RestException(500);
}
return array(
'success' => array(
'code' => 200,
'message' => 'Facture deleted'
)
);
}
/**
* Validate fields before create or update object
*
* @param array $data Datas to validate
* @return array
*
* @throws RestException
*/
function _validate($data)
{
$invoice = array();
foreach (InvoiceApi::$FIELDS as $field) {
if (!isset($data[$field]))
throw new RestException(400, "$field field missing");
$invoice[$field] = $data[$field];
}
return $invoice;
}
}

View File

@ -331,163 +331,189 @@ function restrictedArea($user, $features, $objectid=0, $dbtablename='', $feature
// is linked to a company allowed to $user.
if (! empty($objectid) && $objectid > 0)
{
foreach ($featuresarray as $feature)
{
$sql='';
$check = array('adherent','banque','user','usergroup','produit','service','produit|service','categorie'); // Test on entity only (Objects with no link to company)
$checksoc = array('societe'); // Test for societe object
$checkother = array('contact'); // Test on entity and link to societe. Allowed if link is empty (Ex: contacts...).
$checkproject = array('projet'); // Test for project object
$nocheck = array('barcode','stock','fournisseur'); // No test
$checkdefault = 'all other not already defined'; // Test on entity and link to third party. Not allowed if link is empty (Ex: invoice, orders...).
// If dbtable not defined, we use same name for table than module name
if (empty($dbtablename)) $dbtablename = $feature;
// Check permission for object with entity
if (in_array($feature,$check))
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
if (($feature == 'user' || $feature == 'usergroup') && ! empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && ! $user->entity)
{
$sql.= " AND dbt.entity IS NOT NULL";
}
else
{
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checksoc)) // We check feature = checksoc
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
if ($user->societe_id <> $objectid) accessforbidden();
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
$sql = "SELECT sc.fk_soc";
$sql.= " FROM (".MAIN_DB_PREFIX."societe_commerciaux as sc";
$sql.= ", ".MAIN_DB_PREFIX."societe as s)";
$sql.= " WHERE sc.fk_soc = ".$objectid;
$sql.= " AND sc.fk_user = ".$user->id;
$sql.= " AND sc.fk_soc = s.rowid";
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT s.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX."societe as s";
$sql.= " WHERE s.rowid = ".$objectid;
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checkother))
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.fk_soc = ".$user->societe_id;
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON dbt.fk_soc = sc.fk_soc AND sc.fk_user = '".$user->id."'";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND (dbt.fk_soc IS NULL OR sc.fk_soc IS NOT NULL)"; // Contact not linked to a company or to a company of user
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checkproject))
{
if (! empty($conf->projet->enabled) && ! $user->rights->projet->all->lire)
{
include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
$projectstatic=new Project($db);
$tmps=$projectstatic->getProjectsAuthorizedForUser($user,0,1,0);
$tmparray=explode(',',$tmps);
if (! in_array($objectid,$tmparray)) accessforbidden();
}
else
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (! in_array($feature,$nocheck)) // By default we check with link to third party
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined');
$sql = "SELECT dbt.".$dbt_keyfield;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.".$dbt_keyfield." = ".$user->societe_id;
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined');
$sql = "SELECT sc.fk_soc";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= ", ".MAIN_DB_PREFIX."societe as s";
$sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND sc.fk_soc = dbt.".$dbt_keyfield;
$sql.= " AND dbt.".$dbt_keyfield." = s.rowid";
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
$sql.= " AND sc.fk_user = ".$user->id;
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
//print "sql=".$sql."<br>";
if ($sql)
{
$resql=$db->query($sql);
if ($resql)
{
if ($db->num_rows($resql) == 0) accessforbidden();
}
else
{
accessforbidden();
}
}
}
$ok = checkUserAccessToObject($user, $featuresarray,$objectid,$dbtablename,$feature2,$dbt_keyfield,$dbt_select);
return $ok ? 1 : accessforbidden();
}
return 1;
}
/**
* Check access by user to object
*
* @param User $user User to check
* @param array $featuresarray Features/modules to check
* @param int $objectid Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional).
* @param string $dbtablename 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity. Not used if objectid is null (optional)
* @param string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'.
* @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional)
* @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional)
*
* @return bool True if user has access, False otherwise
*/
function checkUserAccessToObject($user, $featuresarray, $objectid=0, $dbtablename='', $feature2='', $dbt_keyfield='', $dbt_select='')
{
global $db, $conf;
// More parameters
$params = explode('&', $dbtablename);
$dbtablename=(! empty($params[0]) ? $params[0] : '');
$sharedelement=(! empty($params[1]) ? $params[1] : $dbtablename);
foreach ($featuresarray as $feature)
{
$sql='';
$check = array('adherent','banque','user','usergroup','produit','service','produit|service','categorie'); // Test on entity only (Objects with no link to company)
$checksoc = array('societe'); // Test for societe object
$checkother = array('contact'); // Test on entity and link to societe. Allowed if link is empty (Ex: contacts...).
$checkproject = array('projet'); // Test for project object
$nocheck = array('barcode','stock','fournisseur'); // No test
$checkdefault = 'all other not already defined'; // Test on entity and link to third party. Not allowed if link is empty (Ex: invoice, orders...).
// If dbtable not defined, we use same name for table than module name
if (empty($dbtablename)) $dbtablename = $feature;
// Check permission for object with entity
if (in_array($feature,$check))
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
if (($feature == 'user' || $feature == 'usergroup') && ! empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && ! $user->entity)
{
$sql.= " AND dbt.entity IS NOT NULL";
}
else
{
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checksoc)) // We check feature = checksoc
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
if ($user->societe_id <> $objectid) return false;
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
$sql = "SELECT sc.fk_soc";
$sql.= " FROM (".MAIN_DB_PREFIX."societe_commerciaux as sc";
$sql.= ", ".MAIN_DB_PREFIX."societe as s)";
$sql.= " WHERE sc.fk_soc = ".$objectid;
$sql.= " AND sc.fk_user = ".$user->id;
$sql.= " AND sc.fk_soc = s.rowid";
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT s.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX."societe as s";
$sql.= " WHERE s.rowid = ".$objectid;
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checkother))
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.fk_soc = ".$user->societe_id;
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON dbt.fk_soc = sc.fk_soc AND sc.fk_user = '".$user->id."'";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND (dbt.fk_soc IS NULL OR sc.fk_soc IS NOT NULL)"; // Contact not linked to a company or to a company of user
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT dbt.rowid";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (in_array($feature,$checkproject))
{
if (! empty($conf->projet->enabled) && ! $user->rights->projet->all->lire)
{
include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
$projectstatic=new Project($db);
$tmps=$projectstatic->getProjectsAuthorizedForUser($user,0,1,0);
$tmparray=explode(',',$tmps);
if (! in_array($objectid,$tmparray)) return false;
}
else
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
else if (! in_array($feature,$nocheck)) // By default we check with link to third party
{
// If external user: Check permission for external users
if ($user->societe_id > 0)
{
if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined');
$sql = "SELECT dbt.".$dbt_keyfield;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.rowid = ".$objectid;
$sql.= " AND dbt.".$dbt_keyfield." = ".$user->societe_id;
}
// If internal user: Check permission for internal users that are restricted on their objects
else if (! empty($conf->societe->enabled) && ($user->rights->societe->lire && ! $user->rights->societe->client->voir))
{
if (empty($dbt_keyfield)) dol_print_error('','Param dbt_keyfield is required but not defined');
$sql = "SELECT sc.fk_soc";
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= ", ".MAIN_DB_PREFIX."societe as s";
$sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND sc.fk_soc = dbt.".$dbt_keyfield;
$sql.= " AND dbt.".$dbt_keyfield." = s.rowid";
$sql.= " AND s.entity IN (".getEntity($sharedelement, 1).")";
$sql.= " AND sc.fk_user = ".$user->id;
}
// If multicompany and internal users with all permissions, check user is in correct entity
else if (! empty($conf->multicompany->enabled))
{
$sql = "SELECT dbt.".$dbt_select;
$sql.= " FROM ".MAIN_DB_PREFIX.$dbtablename." as dbt";
$sql.= " WHERE dbt.".$dbt_select." = ".$objectid;
$sql.= " AND dbt.entity IN (".getEntity($sharedelement, 1).")";
}
}
//print "sql=".$sql."<br>";
if ($sql)
{
$resql=$db->query($sql);
if ($resql)
{
if ($db->num_rows($resql) == 0) return false;
}
else
{
return false;
}
}
}
return true;
}
/**
* Show a message to say access is forbidden and stop program

View File

@ -0,0 +1,252 @@
<?php
/* Copyright (C) 2003 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \defgroup api Module Api
* \brief Descriptor file for Api modulee
* \file htdocs/api/core/modules/modApi.class.php
* \ingroup api
* \brief Description and activation file for module Api
*/
include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php';
/**
* Description and activation class for module Api
*/
class modApi extends DolibarrModules
{
/**
* Constructor. Define names, constants, directories, boxes, permissions
*
* @param DoliDB $db Database handler
*/
function __construct($db)
{
global $langs,$conf;
$this->db = $db;
// Id for module (must be unique).
// Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id).
$this->numero = 2610;
// Key text used to identify module (for permissions, menus, etc...)
$this->rights_class = 'api';
// Family can be 'crm','financial','hr','projects','products','ecm','technic','other'
// It is used to group modules in module setup page
$this->family = "technic";
// Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
$this->name = preg_replace('/^mod/i','',get_class($this));
// Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module)
$this->description = "REST interface";
// Possible values for version are: 'development', 'experimental', 'dolibarr' or 'dolibarr_deprecated' or version
$this->version = 'development';
// Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase)
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
// Where to store the module in setup page (0=common,1=interface,2=others,3=very specific)
$this->special = 1;
// Name of image file used for this module.
// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
$this->picto='technic';
$this->module_parts = array();
// Data directories to create when module is enabled.
// Example: this->dirs = array("/api/temp");
$this->dirs = array();
// Config pages. Put here list of php page, stored into api/admin directory, to use to setup module.
$this->config_page_url = array("api.php@api");
// Dependencies
$this->hidden = false; // A condition to hide module
$this->depends = array(); // List of modules id that must be enabled if this module is enabled
$this->requiredby = array(); // List of modules id to disable if this one is disabled
$this->conflictwith = array(); // List of modules id this module is in conflict with
$this->phpmin = array(5,3); // Minimum version of PHP required by module
$this->langfiles = array("other");
// Constants
// List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
// Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',1),
// 1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0, 'current', 1)
// );
$this->const = array();
// Array to add new pages in new tabs
// Example: $this->tabs = array('objecttype:+tabname1:Title1:mylangfile@api:$user->rights->api->read:/api/mynewtab1.php?id=__ID__', // To add a new tab identified by code tabname1
// 'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@api:$user->rights->othermodule->read:/api/mynewtab2.php?id=__ID__', // To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key.
// 'objecttype:-tabname:NU:conditiontoremove'); // To remove an existing tab identified by code tabname
// where objecttype can be
// 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member)
// 'contact' to add a tab in contact view
// 'contract' to add a tab in contract view
// 'group' to add a tab in group view
// 'intervention' to add a tab in intervention view
// 'invoice' to add a tab in customer invoice view
// 'invoice_supplier' to add a tab in supplier invoice view
// 'member' to add a tab in fundation member view
// 'opensurveypoll' to add a tab in opensurvey poll view
// 'order' to add a tab in customer order view
// 'order_supplier' to add a tab in supplier order view
// 'payment' to add a tab in payment view
// 'payment_supplier' to add a tab in supplier payment view
// 'product' to add a tab in product view
// 'propal' to add a tab in propal view
// 'project' to add a tab in project view
// 'stock' to add a tab in stock view
// 'thirdparty' to add a tab in third party view
// 'user' to add a tab in user view
$this->tabs = array();
// Dictionaries
if (! isset($conf->api->enabled))
{
$conf->api=new stdClass();
$conf->api->enabled=0;
}
$this->dictionaries=array();
/* Example:
if (! isset($conf->api->enabled)) $conf->api->enabled=0; // This is to avoid warnings
$this->dictionaries=array(
'langs'=>'mylangfile@api',
'tabname'=>array(MAIN_DB_PREFIX."table1",MAIN_DB_PREFIX."table2",MAIN_DB_PREFIX."table3"), // List of tables we want to see into dictonnary editor
'tablib'=>array("Table1","Table2","Table3"), // Label of tables
'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table1 as f','SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table2 as f','SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table3 as f'), // Request to select fields
'tabsqlsort'=>array("label ASC","label ASC","label ASC"), // Sort order
'tabfield'=>array("code,label","code,label","code,label"), // List of fields (result of select to show dictionary)
'tabfieldvalue'=>array("code,label","code,label","code,label"), // List of fields (list of fields to edit a record)
'tabfieldinsert'=>array("code,label","code,label","code,label"), // List of fields (list of fields for insert)
'tabrowid'=>array("rowid","rowid","rowid"), // Name of columns with primary key (try to always name it 'rowid')
'tabcond'=>array($conf->api->enabled,$conf->api->enabled,$conf->api->enabled) // Condition to show each dictionary
);
*/
// Boxes
// Add here list of php file(s) stored in core/boxes that contains class to show a box.
$this->boxes = array(); // List of boxes
// Example:
//$this->boxes=array(array(0=>array('file'=>'myboxa.php','note'=>'','enabledbydefaulton'=>'Home'),1=>array('file'=>'myboxb.php','note'=>''),2=>array('file'=>'myboxc.php','note'=>'')););
// Permissions
$this->rights = array(); // Permission array used by this module
$r=0;
// Add here list of permission defined by an id, a label, a boolean and two constant strings.
// Example:
// $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
// $this->rights[$r][1] = 'Permision label'; // Permission label
// $this->rights[$r][3] = 1; // Permission by default for new user (0/1)
// $this->rights[$r][4] = 'level1'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2)
// $this->rights[$r][5] = 'level2'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2)
// $r++;
// Main menu entries
$this->menu = array(); // List of menus to add
$r=0;
// Add here entries to declare new menus
//
// Example to declare a new Top Menu entry and its Left menu entry:
// $this->menu[$r]=array( 'fk_menu'=>0, // Put 0 if this is a top menu
// 'type'=>'top', // This is a Top menu entry
// 'titre'=>'Api top menu',
// 'mainmenu'=>'api',
// 'leftmenu'=>'api',
// 'url'=>'/api/pagetop.php',
// 'langs'=>'mylangfile@api', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
// 'position'=>100,
// 'enabled'=>'$conf->api->enabled', // Define condition to show or hide menu entry. Use '$conf->api->enabled' if entry must be visible if module is enabled.
// 'perms'=>'1', // Use 'perms'=>'$user->rights->api->level1->level2' if you want your menu with a permission rules
// 'target'=>'',
// 'user'=>2); // 0=Menu for internal users, 1=external users, 2=both
// $r++;
//
// Example to declare a Left Menu entry into an existing Top menu entry:
// $this->menu[$r]=array( 'fk_menu'=>'fk_mainmenu=xxx', // Use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
// 'type'=>'left', // This is a Left menu entry
// 'titre'=>'Api left menu',
// 'mainmenu'=>'xxx',
// 'leftmenu'=>'api',
// 'url'=>'/api/pagelevel2.php',
// 'langs'=>'mylangfile@api', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
// 'position'=>100,
// 'enabled'=>'$conf->api->enabled', // Define condition to show or hide menu entry. Use '$conf->api->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
// 'perms'=>'1', // Use 'perms'=>'$user->rights->api->level1->level2' if you want your menu with a permission rules
// 'target'=>'',
// 'user'=>2); // 0=Menu for internal users, 1=external users, 2=both
// $r++;
// Exports
$r=1;
// Example:
// $this->export_code[$r]=$this->rights_class.'_'.$r;
// $this->export_label[$r]='CustomersInvoicesAndInvoiceLines'; // Translation key (used only if key ExportDataset_xxx_z not found)
// $this->export_enabled[$r]='1'; // Condition to show export in list (ie: '$user->id==3'). Set to 1 to always show when module is enabled.
// $this->export_permission[$r]=array(array("facture","facture","export"));
// $this->export_fields_array[$r]=array('s.rowid'=>"IdCompany",'s.nom'=>'CompanyName','s.address'=>'Address','s.zip'=>'Zip','s.town'=>'Town','s.fk_pays'=>'Country','s.phone'=>'Phone','s.siren'=>'ProfId1','s.siret'=>'ProfId2','s.ape'=>'ProfId3','s.idprof4'=>'ProfId4','s.code_compta'=>'CustomerAccountancyCode','s.code_compta_fournisseur'=>'SupplierAccountancyCode','f.rowid'=>"InvoiceId",'f.facnumber'=>"InvoiceRef",'f.datec'=>"InvoiceDateCreation",'f.datef'=>"DateInvoice",'f.total'=>"TotalHT",'f.total_ttc'=>"TotalTTC",'f.tva'=>"TotalVAT",'f.paye'=>"InvoicePaid",'f.fk_statut'=>'InvoiceStatus','f.note'=>"InvoiceNote",'fd.rowid'=>'LineId','fd.description'=>"LineDescription",'fd.price'=>"LineUnitPrice",'fd.tva_tx'=>"LineVATRate",'fd.qty'=>"LineQty",'fd.total_ht'=>"LineTotalHT",'fd.total_tva'=>"LineTotalTVA",'fd.total_ttc'=>"LineTotalTTC",'fd.date_start'=>"DateStart",'fd.date_end'=>"DateEnd",'fd.fk_product'=>'ProductId','p.ref'=>'ProductRef');
// $this->export_entities_array[$r]=array('s.rowid'=>"company",'s.nom'=>'company','s.address'=>'company','s.zip'=>'company','s.town'=>'company','s.fk_pays'=>'company','s.phone'=>'company','s.siren'=>'company','s.siret'=>'company','s.ape'=>'company','s.idprof4'=>'company','s.code_compta'=>'company','s.code_compta_fournisseur'=>'company','f.rowid'=>"invoice",'f.facnumber'=>"invoice",'f.datec'=>"invoice",'f.datef'=>"invoice",'f.total'=>"invoice",'f.total_ttc'=>"invoice",'f.tva'=>"invoice",'f.paye'=>"invoice",'f.fk_statut'=>'invoice','f.note'=>"invoice",'fd.rowid'=>'invoice_line','fd.description'=>"invoice_line",'fd.price'=>"invoice_line",'fd.total_ht'=>"invoice_line",'fd.total_tva'=>"invoice_line",'fd.total_ttc'=>"invoice_line",'fd.tva_tx'=>"invoice_line",'fd.qty'=>"invoice_line",'fd.date_start'=>"invoice_line",'fd.date_end'=>"invoice_line",'fd.fk_product'=>'product','p.ref'=>'product');
// $this->export_sql_start[$r]='SELECT DISTINCT ';
// $this->export_sql_end[$r] =' FROM ('.MAIN_DB_PREFIX.'facture as f, '.MAIN_DB_PREFIX.'facturedet as fd, '.MAIN_DB_PREFIX.'societe as s)';
// $this->export_sql_end[$r] .=' LEFT JOIN '.MAIN_DB_PREFIX.'product as p on (fd.fk_product = p.rowid)';
// $this->export_sql_end[$r] .=' WHERE f.fk_soc = s.rowid AND f.rowid = fd.fk_facture';
// $this->export_sql_order[$r] .=' ORDER BY s.nom';
// $r++;
}
/**
* Function called when module is enabled.
* The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
* It also creates data directories
*
* @param string $options Options when enabling module ('', 'noboxes')
* @return int 1 if OK, 0 if KO
*/
function init($options='')
{
$sql = array();
$result=$this->_load_tables('/api/sql/');
return $this->_init($sql, $options);
}
/**
* Function called when module is disabled.
* Remove from database constants, boxes and permissions from Dolibarr database.
* Data directories are not deleted
*
* @param string $options Options when enabling module ('', 'noboxes')
* @return int 1 if OK, 0 if KO
*/
function remove($options='')
{
$sql = array();
return $this->_remove($sql, $options);
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\iCache;
/**
* Class ApcCache provides an APC based cache for Restler
*
* @category Framework
* @package Restler
* @author Joel R. Simpson <joel.simpson@gmail.com>
* @copyright 2013 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ApcCache implements iCache
{
/**
* The namespace that all of the cached entries will be stored under. This allows multiple APIs to run concurrently.
*
* @var string
*/
static public $namespace = 'restler';
/**
* store data in the cache
*
*
* @param string $name
* @param mixed $data
*
* @return boolean true if successful
*/
public function set($name, $data)
{
function_exists('apc_store') || $this->apcNotAvailable();
try {
return apc_store(self::$namespace . "-" . $name, $data);
} catch
(\Exception $exception) {
return false;
}
}
private function apcNotAvailable()
{
throw new \Exception('APC is not available for use as Restler Cache. Please make sure the module is installed. http://php.net/manual/en/apc.installation.php');
}
/**
* retrieve data from the cache
*
*
* @param string $name
* @param bool $ignoreErrors
*
* @throws \Exception
* @return mixed
*/
public function get($name, $ignoreErrors = false)
{
function_exists('apc_fetch') || $this->apcNotAvailable();
try {
return apc_fetch(self::$namespace . "-" . $name);
} catch (\Exception $exception) {
if (!$ignoreErrors) {
throw $exception;
}
return null;
}
}
/**
* delete data from the cache
*
*
* @param string $name
* @param bool $ignoreErrors
*
* @throws \Exception
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false)
{
function_exists('apc_delete') || $this->apcNotAvailable();
try {
apc_delete(self::$namespace . "-" . $name);
} catch (\Exception $exception) {
if (!$ignoreErrors) {
throw $exception;
}
}
}
/**
* check if the given name is cached
*
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name)
{
function_exists('apc_exists') || $this->apcNotAvailable();
return apc_exists(self::$namespace . "-" . $name);
}
}

View File

@ -0,0 +1,438 @@
<?php
namespace Luracast\Restler {
/**
* Class that implements spl_autoload facilities and multiple
* conventions support.
* Supports composer libraries and 100% PSR-0 compliant.
* In addition we enable namespace prefixing and class aliases.
*
* @category Framework
* @package Restler
* @subpackage helper
* @author Nick Lombard <github@jigsoft.co.za>
* @copyright 2012 Luracast
* @version 3.0.0rc5
*/
class AutoLoader
{
protected static $instance, // the singleton instance reference
$perfectLoaders, // used to keep the ideal list of loaders
$rogueLoaders = array(), // other auto loaders now unregistered
$classMap = array(), // the class to include file mapping
$aliases = array( // aliases and prefixes instead of null list aliases
'Luracast\\Restler' => null,
'Luracast\\Restler\\Format' => null,
'Luracast\\Restler\\Data' => null,
'Luracast\\Restler\\Filter' => null,
);
/**
* Singleton instance facility.
*
* @static
* @return AutoLoader the current instance or new instance if none exists.
*/
public static function instance()
{
static::$instance = static::$instance ?: new static();
return static::thereCanBeOnlyOne();
}
/**
* Helper function to add a path to the include path.
* AutoLoader uses the include path to discover classes.
*
* @static
*
* @param $path string absolute or relative path.
*
* @return bool false if the path cannot be resolved
* or the resolved absolute path.
*/
public static function addPath($path) {
if (false === $path = stream_resolve_include_path($path))
return false;
else
set_include_path($path.PATH_SEPARATOR.get_include_path());
return $path;
}
/**
* Other autoLoaders interfere and cause duplicate class loading.
* AutoLoader is capable enough to handle all standards so no need
* for others stumbling about.
*
* @return callable the one true auto loader.
*/
public static function thereCanBeOnlyOne() {
if (static::$perfectLoaders === spl_autoload_functions())
return static::$instance;
if (false !== $loaders = spl_autoload_functions())
if (0 < $count = count($loaders))
for ($i = 0, static::$rogueLoaders += $loaders;
$i < $count && false != ($loader = $loaders[$i]);
$i++)
if ($loader !== static::$perfectLoaders[0])
spl_autoload_unregister($loader);
return static::$instance;
}
/**
* Seen this before cache handler.
* Facilitates both lookup and persist operations as well as convenience,
* load complete map functionality. The key can only be given a non falsy
* value once, this will be truthy for life.
*
* @param $key mixed class name considered or a collection of
* classMap entries
* @param $value mixed optional not required when doing a query on
* key. Default is false we haven't seen this
* class. Most of the time it will be the filename
* for include and is set to true if we are unable
* to load this class iow true == it does not exist.
* value may also be a callable auto loader function.
*
* @return mixed The known value for the key or false if key has no value
*/
public static function seen($key, $value = false)
{
if (is_array($key)) {
static::$classMap = $key + static::$classMap;
return false;
}
if (empty(static::$classMap[$key]))
static::$classMap[$key] = $value;
if (is_string($alias = static::$classMap[$key]))
if (isset(static::$classMap[$alias]))
return static::$classMap[$alias];
return static::$classMap[$key];
}
/**
* Protected constructor to enforce singleton pattern.
* Populate a default include path.
* All possible includes cant possibly be catered for and if you
* require another path then simply add it calling set_include_path.
*/
protected function __construct()
{
static::$perfectLoaders = array($this);
if (false === static::seen('__include_path')) {
$paths = explode(PATH_SEPARATOR, get_include_path());
$slash = DIRECTORY_SEPARATOR;
$dir = dirname(__DIR__);
$source_dir = dirname($dir);
$dir = dirname($source_dir);
foreach (
array(
array($source_dir),
array($dir, '..', '..', 'composer'),
array($dir, 'vendor', 'composer'),
array($dir, '..', '..', '..', 'php'),
array($dir, 'vendor', 'php'))
as $includePath)
if (false !== $path = stream_resolve_include_path(
implode($slash, $includePath)
))
if ('composer' == end($includePath) &&
false !== $classmapPath = stream_resolve_include_path(
"$path{$slash}autoload_classmap.php"
)
) {
static::seen(static::loadFile(
$classmapPath
));
$paths = array_merge(
$paths,
array_values(static::loadFile(
"$path{$slash}autoload_namespaces.php"
))
);
} else
$paths[] = $path;
$paths = array_filter(array_map(
function ($path) {
if (false == $realPath = @realpath($path))
return null;
return $realPath . DIRECTORY_SEPARATOR;
},
$paths
));
natsort($paths);
static::seen(
'__include_path',
implode(PATH_SEPARATOR, array_unique($paths))
);
}
set_include_path(static::seen('__include_path'));
}
/**
* Attempt to include the path location.
* Called from a static context which will not expose the AutoLoader
* instance itself.
*
* @param $path string location of php file on the include path
*
* @return bool|mixed returns reference obtained from the include or false
*/
private static function loadFile($path)
{
return \Luracast_Restler_autoloaderInclude($path);
}
/**
* Attempt to load class with namespace prefixes.
*
* @param $className string class name
*
* @return bool|mixed reference to discovered include or false
*/
private function loadPrefixes($className)
{
$currentClass = $className;
if (false !== $pos = strrpos($className, '\\'))
$className = substr($className, $pos);
else
$className = "\\$className";
for (
$i = 0,
$file = false,
$count = count(static::$aliases),
$prefixes = array_keys(static::$aliases);
$i < $count
&& false === $file
&& false === $file = $this->discover(
$variant = $prefixes[$i++].$className,
$currentClass
);
$file = $this->loadAliases($variant)
);
return $file;
}
/**
* Attempt to load configured aliases based on namespace part of class name.
*
* @param $className string fully qualified class name.
*
* @return bool|mixed reference to discovered include or false
*/
private function loadAliases($className)
{
$file = false;
if (preg_match('/(.+)(\\\\\w+$)/U', $className, $parts))
for (
$i = 0,
$aliases = isset(static::$aliases[$parts[1]])
? static::$aliases[$parts[1]] : array(),
$count = count($aliases);
$i < $count && false === $file;
$file = $this->discover(
"{$aliases[$i++]}$parts[2]",
$className
)
) ;
return $file;
}
/**
* Load from rogueLoaders as last resort.
* It may happen that a custom auto loader may load classes in a unique way,
* these classes cannot be seen otherwise nor should we attempt to cover every
* possible deviation. If we still can't find a class, as a last resort, we will
* run through the list of rogue loaders and verify if we succeeded.
*
* @param $className string className that can't be found
* @param null $loader callable loader optional when the loader is known
*
* @return bool false unless className now exists
*/
private function loadLastResort($className, $loader = null) {
$loaders = array_unique(static::$rogueLoaders);
if (isset($loader)) {
if (false === array_search($loader, $loaders))
static::$rogueLoaders[] = $loader;
return $this->loadThisLoader($className, $loader);
}
foreach ($loaders as $loader)
if (false !== $file = $this->loadThisLoader($className, $loader))
return $file;
return false;
}
/**
* Helper for loadLastResort.
* Use loader with $className and see if className exists.
*
* @param $className string name of a class to load
* @param $loader callable autoLoader method
*
* @return bool false unless className exists
*/
private function loadThisLoader($className, $loader) {
if (is_callable($loader)
&& false !== $file = $loader($className)
&& $this->exists($className, $loader))
return $file;
return false;
}
/**
* Create an alias for class.
*
* @param $className string the name of the alias class
* @param $currentClass string the current class this alias references
*/
private function alias($className, $currentClass)
{
if ($className != $currentClass
&& false !== strpos($className, $currentClass))
if (!class_exists($currentClass, false)
&& class_alias($className, $currentClass))
static::seen($currentClass, $className);
}
/**
* Discovery process.
*
* @param $className string class name to discover
* @param $currentClass string optional name of current class when
* looking up an alias
*
* @return bool|mixed resolved include reference or false
*/
private function discover($className, $currentClass = null)
{
$currentClass = $currentClass ?: $className;
/** The short version we've done this before and found it in cache */
if (false !== $file = static::seen($className)) {
if (!$this->exists($className))
if (is_callable($file))
$file = $this->loadLastResort($className, $file);
elseif($file = stream_resolve_include_path($file))
$file = static::loadFile($file);
$this->alias($className, $currentClass);
return $file;
}
/** We did not find it in cache, lets look for it shall we */
/** replace \ with / and _ in CLASS NAME with / = PSR-0 in 3 lines */
$file = preg_replace("/\\\|_(?=\w+$)/", DIRECTORY_SEPARATOR, $className);
if (false === $file = stream_resolve_include_path("$file.php"))
return false;
/** have we loaded this file before could this be an alias */
if (in_array($file, get_included_files())) {
if (false !== $sameFile = array_search($file, static::$classMap))
if (!$this->exists($className, $file))
if (false !== strpos($sameFile, $className))
$this->alias($sameFile, $className);
return $file;
}
$state = array_merge(get_declared_classes(), get_declared_interfaces());
if (false !== $result = static::loadFile($file)) {
if ($this->exists($className, $file))
$this->alias($className, $currentClass);
elseif (false != $diff = array_diff(
array_merge(get_declared_classes(), get_declared_interfaces()), $state))
foreach ($diff as $autoLoaded)
if ($this->exists($autoLoaded, $file))
if (false !== strpos($autoLoaded, $className))
$this->alias($autoLoaded, $className);
if (!$this->exists($currentClass))
$result = false;
}
return $result;
}
/**
* Checks whether supplied string exists in a loaded class or interface.
* As a convenience the supplied $mapping can be the value for seen.
*
* @param $className string The class or interface to verify
* @param $mapping string (optional) value for map/seen if found to exist
*
* @return bool whether the class/interface exists without calling auto loader
*/
private function exists($className, $mapping = null)
{
if (class_exists($className, false)
|| interface_exists($className, false))
if (isset($mapping))
return static::seen($className, $mapping);
else
return true;
return false;
}
/**
* Auto loader callback through __invoke object as function.
*
* @param $className string class/interface name to auto load
*
* @return mixed|null the reference from the include or null
*/
public function __invoke($className)
{
if (empty($className))
return false;
if (false !== $includeReference = $this->discover($className))
return $includeReference;
static::thereCanBeOnlyOne();
if (false !== $includeReference = $this->loadAliases($className))
return $includeReference;
if (false !== $includeReference = $this->loadPrefixes($className))
return $includeReference;
if (false !== $includeReference = $this->loadLastResort($className))
return $includeReference;
static::seen($className, true);
return null;
}
}
}
namespace {
/**
* Include function in the root namespace to include files optimized
* for the global context.
*
* @param $path string path of php file to include into the global context.
*
* @return mixed|bool false if the file could not be included.
*/
function Luracast_Restler_autoloaderInclude($path) {
return include $path;
}
}

View File

@ -0,0 +1,466 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Parses the PHPDoc comments for metadata. Inspired by `Documentor` code base.
*
* @category Framework
* @package Restler
* @subpackage Helper
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class CommentParser
{
/**
* name for the embedded data
*
* @var string
*/
public static $embeddedDataName = 'properties';
/**
* Regular Expression pattern for finding the embedded data and extract
* the inner information. It is used with preg_match.
*
* @var string
*/
public static $embeddedDataPattern
= '/```(\w*)[\s]*(([^`]*`{0,2}[^`]+)*)```/ms';
/**
* Pattern will have groups for the inner details of embedded data
* this index is used to locate the data portion.
*
* @var int
*/
public static $embeddedDataIndex = 2;
/**
* Delimiter used to split the array data.
*
* When the name portion is of the embedded data is blank auto detection
* will be used and if URLEncodedFormat is detected as the data format
* the character specified will be used as the delimiter to find split
* array data.
*
* @var string
*/
public static $arrayDelimiter = ',';
/**
* character sequence used to escape \@
*/
const escapedAtChar = '\\@';
/**
* character sequence used to escape end of comment
*/
const escapedCommendEnd = '{@*}';
/**
* Instance of Restler class injected at runtime.
*
* @var Restler
*/
public $restler;
/**
* Comment information is parsed and stored in to this array.
*
* @var array
*/
private $_data = array();
/**
* Parse the comment and extract the data.
*
* @static
*
* @param $comment
* @param bool $isPhpDoc
*
* @return array associative array with the extracted values
*/
public static function parse($comment, $isPhpDoc = true)
{
$p = new self();
if (empty($comment)) {
return $p->_data;
}
if ($isPhpDoc) {
$comment = self::removeCommentTags($comment);
}
$p->extractData($comment);
return $p->_data;
}
/**
* Removes the comment tags from each line of the comment.
*
* @static
*
* @param string $comment PhpDoc style comment
*
* @return string comments with out the tags
*/
public static function removeCommentTags($comment)
{
$pattern = '/(^\/\*\*)|(^\s*\**[ \/]?)|\s(?=@)|\s\*\//m';
return preg_replace($pattern, '', $comment);
}
/**
* Extracts description and long description, uses other methods to get
* parameters.
*
* @param $comment
*
* @return array
*/
private function extractData($comment)
{
//to use @ as part of comment we need to
$comment = str_replace(
array(self::escapedCommendEnd, self::escapedAtChar),
array('*/', '@'),
$comment);
$description = array();
$longDescription = array();
$params = array();
$mode = 0; // extract short description;
$comments = preg_split("/(\r?\n)/", $comment);
// remove first blank line;
array_shift($comments);
$addNewline = false;
foreach ($comments as $line) {
$line = trim($line);
$newParam = false;
if (empty ($line)) {
if ($mode == 0) {
$mode++;
} else {
$addNewline = true;
}
continue;
} elseif ($line{0} == '@') {
$mode = 2;
$newParam = true;
}
switch ($mode) {
case 0 :
$description[] = $line;
if (count($description) > 3) {
// if more than 3 lines take only first line
$longDescription = $description;
$description[] = array_shift($longDescription);
$mode = 1;
} elseif (substr($line, -1) == '.') {
$mode = 1;
}
break;
case 1 :
if ($addNewline) {
$line = ' ' . $line;
}
$longDescription[] = $line;
break;
case 2 :
$newParam
? $params[] = $line
: $params[count($params) - 1] .= ' ' . $line;
}
$addNewline = false;
}
$description = implode(' ', $description);
$longDescription = implode(' ', $longDescription);
$description = preg_replace('/\s+/msu', ' ', $description);
$longDescription = preg_replace('/\s+/msu', ' ', $longDescription);
list($description, $d1)
= $this->parseEmbeddedData($description);
list($longDescription, $d2)
= $this->parseEmbeddedData($longDescription);
$this->_data = compact('description', 'longDescription');
$d2 += $d1;
if (!empty($d2)) {
$this->_data[self::$embeddedDataName] = $d2;
}
foreach ($params as $key => $line) {
list(, $param, $value) = preg_split('/\@|\s/', $line, 3)
+ array('', '', '');
list($value, $embedded) = $this->parseEmbeddedData($value);
$value = array_filter(preg_split('/\s+/msu', $value));
$this->parseParam($param, $value, $embedded);
}
return $this->_data;
}
/**
* Parse parameters that begin with (at)
*
* @param $param
* @param array $value
* @param array $embedded
*/
private function parseParam($param, array $value, array $embedded)
{
$data = & $this->_data;
$allowMultiple = false;
switch ($param) {
case 'param' :
$value = $this->formatParam($value);
$allowMultiple = true;
break;
case 'var' :
$value = $this->formatVar($value);
break;
case 'return' :
$value = $this->formatReturn($value);
break;
case 'class' :
$data = & $data[$param];
list ($param, $value) = $this->formatClass($value);
break;
case 'access' :
$value = reset($value);
break;
case 'expires' :
case 'status' :
$value = intval(reset($value));
break;
case 'throws' :
$value = $this->formatThrows($value);
$allowMultiple = true;
break;
case 'author':
$value = $this->formatAuthor($value);
$allowMultiple = true;
break;
case 'header' :
case 'link':
case 'example':
case 'todo':
$allowMultiple = true;
//don't break, continue with code for default:
default :
$value = implode(' ', $value);
}
if (!empty($embedded)) {
if (is_string($value)) {
$value = array('description' => $value);
}
$value[self::$embeddedDataName] = $embedded;
}
if (empty ($data[$param])) {
if ($allowMultiple) {
$data[$param] = array(
$value
);
} else {
$data[$param] = $value;
}
} elseif ($allowMultiple) {
$data[$param][] = $value;
} elseif ($param == 'param') {
$arr = array(
$data[$param],
$value
);
$data[$param] = $arr;
} else {
if (!is_string($value) && isset($value[self::$embeddedDataName])
&& isset($data[$param][self::$embeddedDataName])
) {
$value[self::$embeddedDataName]
+= $data[$param][self::$embeddedDataName];
}
$data[$param] = $value + $data[$param];
}
}
/**
* Parses the inline php doc comments and embedded data.
*
* @param $subject
*
* @return array
* @throws Exception
*/
private function parseEmbeddedData($subject)
{
$data = array();
//parse {@pattern } tags specially
while (preg_match('|(?s-m)({@pattern (/.+/[imsxuADSUXJ]*)})|', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$data['pattern'] = $matches[2];
}
while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
if ($matches[2] == 'true' || $matches[2] == 'false') {
$matches[2] = $matches[2] == 'true';
} elseif ($matches[2] == '') {
$matches[2] = true;
}
if ($matches[1] == 'pattern') {
throw new Exception('Inline pattern tag should follow {@pattern /REGEX_PATTERN_HERE/} format and can optionally include PCRE modifiers following the ending `/`');
} elseif (false !== strpos($matches[2], static::$arrayDelimiter)) {
$matches[2] = explode(static::$arrayDelimiter, $matches[2]);
}
$data[$matches[1]] = $matches[2];
}
while (preg_match(self::$embeddedDataPattern, $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$str = $matches[self::$embeddedDataIndex];
if (isset ($this->restler)
&& self::$embeddedDataIndex > 1
&& !empty ($matches[1])
) {
$extension = $matches[1];
$formatMap = $this->restler->getFormatMap();
if (isset ($formatMap[$extension])) {
/**
* @var \Luracast\Restler\Format\iFormat
*/
$format = $formatMap[$extension];
$format = new $format();
$data = $format->decode($str);
}
} else { // auto detect
if ($str{0} == '{') {
$d = json_decode($str, true);
if (json_last_error() != JSON_ERROR_NONE) {
throw new Exception('Error parsing embedded JSON data'
. " $str");
}
$data = $d + $data;
} else {
parse_str($str, $d);
//clean up
$d = array_filter($d);
foreach ($d as $key => $val) {
$kt = trim($key);
if ($kt != $key) {
unset($d[$key]);
$key = $kt;
$d[$key] = $val;
}
if (is_string($val)) {
if ($val == 'true' || $val == 'false') {
$d[$key] = $val == 'true' ? true : false;
} else {
$val = explode(self::$arrayDelimiter, $val);
if (count($val) > 1) {
$d[$key] = $val;
} else {
$d[$key] =
preg_replace('/\s+/msu', ' ',
$d[$key]);
}
}
}
}
$data = $d + $data;
}
}
}
return array($subject, $data);
}
private function formatThrows(array $value)
{
$r = array();
$r['code'] = count($value) && is_numeric($value[0])
? intval(array_shift($value)) : 500;
$reason = implode(' ', $value);
$r['reason'] = empty($reason) ? '' : $reason;
return $r;
}
private function formatClass(array $value)
{
$param = array_shift($value);
if (empty($param)) {
$param = 'Unknown';
}
$value = implode(' ', $value);
return array(
ltrim($param, '\\'),
array('description' => $value)
);
}
private function formatAuthor(array $value)
{
$r = array();
$email = end($value);
if ($email{0} == '<') {
$email = substr($email, 1, -1);
array_pop($value);
$r['email'] = $email;
}
$r['name'] = implode(' ', $value);
return $r;
}
private function formatReturn(array $value)
{
$data = explode('|', array_shift($value));
$r = array(
'type' => count($data) == 1 ? $data[0] : $data
);
$r['description'] = implode(' ', $value);
return $r;
}
private function formatParam(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
$data = array_shift($value);
if (!empty($data) && $data{0} == '$') {
$r['name'] = substr($data, 1);
}
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
private function formatVar(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Luracast\Restler;
/**
* Default Composer to provide standard structure for all HTTP responses
*
* @category Framework
* @package Restler
* @subpackage result
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class Compose implements iCompose
{
/**
* @var bool When restler is not running in production mode, this value will
* be checked to include the debug information on error response
*/
public static $includeDebugInfo = true;
/**
* Current Restler instance
* Injected at runtime
*
* @var Restler
*/
public $restler;
/**
* Result of an api call is passed to this method
* to create a standard structure for the data
*
* @param mixed $result can be a primitive or array or object
*
* @return mixed
*/
public function response($result)
{
//TODO: check Defaults::language and change result accordingly
return $result;
}
/**
* When the api call results in RestException this method
* will be called to return the error message
*
* @param RestException $exception exception that has reasons for failure
*
* @return array
*/
public function message(RestException $exception)
{
//TODO: check Defaults::language and change result accordingly
$r = array(
'error' => array(
'code' => $exception->getCode(),
'message' => $exception->getErrorMessage(),
) + $exception->getDetails()
);
if (!Scope::get('Restler')->getProductionMode() && self::$includeDebugInfo) {
$r += array(
'debug' => array(
'source' => $exception->getSource(),
'stages' => $exception->getStages(),
)
);
}
return $r;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Luracast\Restler\Data;
/**
* ValueObject for api method info. All needed information about a api method
* is stored here
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ApiMethodInfo extends ValueObject
{
/**
* @var string target url
*/
public $url;
/**
* @var string
*/
public $className;
/**
* @var string
*/
public $methodName;
/**
* @var array parameters to be passed to the api method
*/
public $parameters = array();
/**
* @var array information on parameters in the form of array(name => index)
*/
public $arguments = array();
/**
* @var array default values for parameters if any
* in the form of array(index => value)
*/
public $defaults = array();
/**
* @var array key => value pair of method meta information
*/
public $metadata = array();
/**
* @var int access level
* 0 - @public - available for all
* 1 - @hybrid - both public and protected (enhanced info for authorized)
* 2 - @protected comment - only for authenticated users
* 3 - protected method - only for authenticated users
*/
public $accessLevel = 0;
}

View File

@ -0,0 +1,33 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class for Array manipulation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class Arr
{
/**
* Deep copy given array
*
* @param array $arr
*
* @return array
*/
public static function copy(array $arr)
{
$copy = array();
foreach ($arr as $key => $value) {
if (is_array($value)) $copy[$key] = static::copy($value);
else if (is_object($value)) $copy[$key] = clone $value;
else $copy[$key] = $value;
}
return $copy;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Luracast\Restler\Data;
use Exception;
/**
* Invalid Exception
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Invalid extends Exception
{
}

View File

@ -0,0 +1,157 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class that converts the given object
* in to associative array
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Object
{
/**
* @var bool|string|callable
*/
public static $stringEncoderFunction = false;
/**
* @var bool|string|callable
*/
public static $numberEncoderFunction = false;
/**
* @var array key value pairs for fixing value types using functions.
* For example
*
* 'id'=>'intval' will make sure all values of the id properties
* will be converted to integers intval function
* 'password'=> null will remove all the password entries
*/
public static $fix = array();
/**
* @var string character that is used to identify sub objects
*
* For example
*
* when Object::$separatorChar = '.';
*
* array('my.object'=>true) will result in
*
* array(
* 'my'=>array('object'=>true)
* );
*/
public static $separatorChar = null;
/**
* @var bool set it to true when empty arrays, blank strings, null values
* to be automatically removed from response
*/
public static $removeEmpty = false;
/**
* @var bool set it to true to remove all null values from the result
*/
public static $removeNull = false;
/**
* Convenience function that converts the given object
* in to associative array
*
* @static
*
* @param mixed $object that needs to be converted
*
* @param bool $forceObjectTypeWhenEmpty when set to true outputs
* actual type (array or
* object) rather than
* always an array when the
* array/object is empty
*
* @return array
*/
public static function toArray($object,
$forceObjectTypeWhenEmpty = false)
{
//if ($object instanceof JsonSerializable) { //wont work on PHP < 5.4
if (is_object($object)) {
if (method_exists($object, 'jsonSerialize')) {
$object = $object->jsonSerialize();
} elseif (method_exists($object, '__sleep')) {
$properties = $object->__sleep();
$array = array();
foreach ($properties as $key) {
$value = self::toArray($object->{$key},
$forceObjectTypeWhenEmpty);
if (self::$stringEncoderFunction && is_string($value)) {
$value = self::$stringEncoderFunction ($value);
} elseif (self::$numberEncoderFunction && is_numeric($value)) {
$value = self::$numberEncoderFunction ($value);
}
$array [$key] = $value;
}
return $array;
}
}
if (is_array($object) || is_object($object)) {
$count = 0;
$array = array();
foreach ($object as $key => $value) {
if (
is_string(self::$separatorChar) &&
false !== strpos($key, self::$separatorChar)
) {
list($key, $obj) = explode(self::$separatorChar, $key, 2);
$object[$key][$obj] = $value;
$value = $object[$key];
}
if (self::$removeEmpty && empty($value) && !is_numeric($value) && !is_bool($value)) {
continue;
} elseif (self::$removeNull && is_null($value)) {
continue;
}
if (array_key_exists($key, self::$fix)) {
if (isset(self::$fix[$key])) {
$value = call_user_func(self::$fix[$key], $value);
} else {
continue;
}
}
$value = self::toArray($value, $forceObjectTypeWhenEmpty);
if (self::$stringEncoderFunction && is_string($value)) {
$value = self::$encoderFunctionName ($value);
} elseif (self::$numberEncoderFunction && is_numeric($value)) {
$value = self::$numberEncoderFunction ($value);
}
$array [$key] = $value;
$count++;
}
return $forceObjectTypeWhenEmpty && $count == 0 ? $object : $array;
}
return $object;
}
public function __get($name)
{
isset(self::$fix[$name]) ? self::$fix[$name] : null;
}
public function __set($name, $function)
{
self::$fix[$name] = $function;
}
public function __isset($name)
{
return isset(self::$fix[$name]);
}
public function __unset($name)
{
unset(self::$fix[$name]);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class for String manipulation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class String
{
/**
* Given haystack contains the needle or not?
*
* @param string $haystack
* @param string $needle
* @param bool $caseSensitive
*
* @return bool
*/
public static function contains($haystack, $needle, $caseSensitive = true)
{
if (empty($needle))
return true;
return $caseSensitive
? strpos($haystack, $needle) !== false
: stripos($haystack, $needle) !== false;
}
/**
* Given haystack begins with the needle or not?
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function beginsWith($haystack, $needle)
{
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
/**
* Given haystack ends with the needle or not?
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
{
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
/**
* Convert camelCased or underscored string in to a title
*
* @param string $name
*
* @return string
*/
public static function title($name)
{
return
ucwords(
preg_replace(
array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/', '/(_)/'),
array(' $0', ' $0', ' '),
$name
)
);
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace Luracast\Restler\Data;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Util;
/**
* ValueObject for validation information. An instance is created and
* populated by Restler to pass it to iValidate implementing classes for
* validation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ValidationInfo implements iValueObject
{
/**
* @var mixed given value for the parameter
*/
public $value;
/**
* @var string proper name for given parameter
*/
public $label;
/**
* @var string html element that can be used to represent the parameter for
* input
*/
public $field;
/**
* @var mixed default value for the parameter
*/
public $default;
/**
* Name of the variable being validated
*
* @var string variable name
*/
public $name;
/**
* @var bool is it required or not
*/
public $required;
/**
* @var string body or header or query where this parameter is coming from
* in the http request
*/
public $from;
/**
* Data type of the variable being validated.
* It will be mostly string
*
* @var string|array multiple types are specified it will be of
* type array otherwise it will be a string
*/
public $type;
/**
* When the type is array, this field is used to define the type of the
* contents of the array
*
* @var string|null when all the items in an array are of certain type, we
* can set this property. It will be null if the items can be of any type
*/
public $contentType;
/**
* Should we attempt to fix the value?
* When set to false validation class should throw
* an exception or return false for the validate call.
* When set to true it will attempt to fix the value if possible
* or throw an exception or return false when it cant be fixed.
*
* @var boolean true or false
*/
public $fix = false;
/**
* @var array of children to be validated
*/
public $children = null;
// ==================================================================
//
// VALUE RANGE
//
// ------------------------------------------------------------------
/**
* Given value should match one of the values in the array
*
* @var array of choices to match to
*/
public $choice;
/**
* If the type is string it will set the lower limit for length
* else will specify the lower limit for the value
*
* @var number minimum value
*/
public $min;
/**
* If the type is string it will set the upper limit limit for length
* else will specify the upper limit for the value
*
* @var number maximum value
*/
public $max;
// ==================================================================
//
// REGEX VALIDATION
//
// ------------------------------------------------------------------
/**
* RegEx pattern to match the value
*
* @var string regular expression
*/
public $pattern;
// ==================================================================
//
// CUSTOM VALIDATION
//
// ------------------------------------------------------------------
/**
* Rules specified for the parameter in the php doc comment.
* It is passed to the validation method as the second parameter
*
* @var array custom rule set
*/
public $rules;
/**
* Specifying a custom error message will override the standard error
* message return by the validator class
*
* @var string custom error response
*/
public $message;
// ==================================================================
//
// METHODS
//
// ------------------------------------------------------------------
/**
* Name of the method to be used for validation.
* It will be receiving two parameters $input, $rules (array)
*
* @var string validation method name
*/
public $method;
/**
* Instance of the API class currently being called. It will be null most of
* the time. Only when method is defined it will contain an instance.
* This behavior is for lazy loading of the API class
*
* @var null|object will be null or api class instance
*/
public $apiClassInstance = null;
public static function numericValue($value)
{
return ( int )$value == $value
? ( int )$value
: floatval($value);
}
public static function arrayValue($value)
{
return is_array($value) ? $value : array(
$value
);
}
public static function stringValue($value, $glue = ',')
{
return is_array($value)
? implode($glue, $value)
: ( string )$value;
}
public static function booleanValue($value)
{
return is_bool($value)
? $value
: $value !== 'false';
}
public static function filterArray(array $data, $keepNumericKeys)
{
$r = array();
foreach ($data as $key => $value) {
if (is_numeric($key)) {
if ($keepNumericKeys) {
$r[$key] = $value;
}
} elseif (!$keepNumericKeys) {
$r[$key] = $value;
}
}
return $r;
}
public function __toString()
{
return ' new ValidationInfo() ';
}
private function getProperty(array &$from, $property)
{
$p = Util::nestedValue($from, $property);
unset($from[$property]);
$p2 = Util::nestedValue(
$from, CommentParser::$embeddedDataName, $property
);
unset($from[CommentParser::$embeddedDataName][$property]);
if ($property == 'type' && $p == 'array' && $p2) {
$this->contentType = $p2;
return $p;
}
$r = is_null($p2) ? (is_null($p) ? null : $p) : $p2;
if (!is_null($r)) {
if ($property == 'min' || $property == 'max') {
return static::numericValue($r);
} elseif ($property == 'required' || $property == 'fix') {
return static::booleanValue($r);
} elseif ($property == 'choice') {
return static::arrayValue($r);
} elseif ($property == 'pattern') {
return static::stringValue($r);
}
}
return $r;
}
public function __construct(array $info)
{
$properties = get_object_vars($this);
unset($properties['contentType']);
foreach ($properties as $property => $value) {
$this->{$property} = $this->getProperty($info, $property);
}
$inner = Util::nestedValue($info, 'properties');
$this->rules = !empty($inner) ? $inner + $info : $info;
unset($this->rules['properties']);
if (is_string($this->type) && $this->type == 'integer') {
$this->type = 'int';
}
}
/**
* Magic Method used for creating instance at run time
*/
public static function __set_state(array $info)
{
$o = new self ($info);
return $o;
}
}

View File

@ -0,0 +1,626 @@
<?php
namespace Luracast\Restler\Data;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Format\HtmlFormat;
use Luracast\Restler\RestException;
use Luracast\Restler\Scope;
use Luracast\Restler\Util;
/**
* Default Validator class used by Restler. It can be replaced by any
* iValidate implementing class by setting Defaults::$validatorClass
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Validator implements iValidate
{
public static $holdException = false;
public static $exceptions = array();
/**
* Validate alphabetic characters.
*
* Check that given value contains only alphabetic characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function alpha($input, ValidationInfo $info = null)
{
if (ctype_alpha($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non alpha characters
return preg_replace("/[^a-z]/i", "", $input);
}
throw new Invalid('Expecting only alphabetic characters.');
}
/**
* Validate alpha numeric characters.
*
* Check that given value contains only alpha numeric characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function alphanumeric($input, ValidationInfo $info = null)
{
if (ctype_alnum($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non alpha numeric and space characters
return preg_replace("/[^a-z0-9 ]/i", "", $input);
}
throw new Invalid('Expecting only alpha numeric characters.');
}
/**
* Validate printable characters.
*
* Check that given value contains only printable characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function printable($input, ValidationInfo $info = null)
{
if (ctype_print($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non printable characters
return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $input);
}
throw new Invalid('Expecting only printable characters.');
}
/**
* Validate hexadecimal digits.
*
* Check that given value contains only hexadecimal digits.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function hex($input, ValidationInfo $info = null)
{
if (ctype_xdigit($input)) {
return $input;
}
throw new Invalid('Expecting only hexadecimal digits.');
}
/**
* Validate Telephone number
*
* Check if the given value is numeric with or without a `+` prefix
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function tel($input, ValidationInfo $info = null)
{
if (is_numeric($input) && '-' != substr($input, 0, 1)) {
return $input;
}
throw new Invalid('Expecting phone number, a numeric value ' .
'with optional `+` prefix');
}
/**
* Validate Email
*
* Check if the given string is a valid email
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function email($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_EMAIL);
if ($r) {
return $r;
} elseif ($info && $info->fix) {
$r = filter_var($input, FILTER_SANITIZE_EMAIL);
return static::email($r);
}
throw new Invalid('Expecting email in `name@example.com` format');
}
/**
* Validate IP Address
*
* Check if the given string is a valid ip address
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function ip($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_IP);
if ($r)
return $r;
throw new Invalid('Expecting IP address in IPV6 or IPV4 format');
}
/**
* Validate Url
*
* Check if the given string is a valid url
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function url($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_URL);
if ($r) {
return $r;
} elseif ($info && $info->fix) {
$r = filter_var($input, FILTER_SANITIZE_URL);
return static::url($r);
}
throw new Invalid('Expecting url in `http://example.com` format');
}
/**
* MySQL Date
*
* Check if the given string is a valid date in YYYY-MM-DD format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function date($input, ValidationInfo $info = null)
{
if (
preg_match(
'#^(?P<year>\d{2}|\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$#',
$input,
$date
)
&& checkdate($date['month'], $date['day'], $date['year'])
) {
return $input;
}
throw new Invalid(
'Expecting date in `YYYY-MM-DD` format, such as `'
. date("Y-m-d") . '`'
);
}
/**
* MySQL DateTime
*
* Check if the given string is a valid date and time in YYY-MM-DD HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function datetime($input, ValidationInfo $info = null)
{
if (
preg_match('/^(?P<year>19\d\d|20\d\d)\-(?P<month>0[1-9]|1[0-2])\-' .
'(?P<day>0\d|[1-2]\d|3[0-1]) (?P<h>0\d|1\d|2[0-3]' .
')\:(?P<i>[0-5][0-9])\:(?P<s>[0-5][0-9])$/',
$input, $date)
&& checkdate($date['month'], $date['day'], $date['year'])
) {
return $input;
}
throw new Invalid(
'Expecting date and time in `YYYY-MM-DD HH:MM:SS` format, such as `'
. date("Y-m-d H:i:s") . '`'
);
}
/**
* Alias for Time
*
* Check if the given string is a valid time in HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time24($input, ValidationInfo $info = null)
{
return static::time($input, $info);
}
/**
* Time
*
* Check if the given string is a valid time in HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time($input, ValidationInfo $info = null)
{
if (preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/', $input)) {
return $input;
}
throw new Invalid(
'Expecting time in `HH:MM:SS` format, such as `'
. date("H:i:s") . '`'
);
}
/**
* Time in 12 hour format
*
* Check if the given string is a valid time 12 hour format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time12($input, ValidationInfo $info = null)
{
if (preg_match(
'/^([1-9]|1[0-2]|0[1-9]){1}(:[0-5][0-9])?\s?([aApP][mM]{1})?$/',
$input)
) {
return $input;
}
throw new Invalid(
'Expecting time in 12 hour format, such as `08:00AM` and `10:05:11`'
);
}
/**
* Unix Timestamp
*
* Check if the given value is a valid timestamp
*
* @param String $input
* @param ValidationInfo $info
*
* @return int
* @throws Invalid
*/
public static function timestamp($input, ValidationInfo $info = null)
{
if ((string)(int)$input == $input
&& ($input <= PHP_INT_MAX)
&& ($input >= ~PHP_INT_MAX)
) {
return (int)$input;
}
throw new Invalid('Expecting unix timestamp, such as ' . time());
}
/**
* Validate the given input
*
* Validates the input and attempts to fix it when fix is requested
*
* @param mixed $input
* @param ValidationInfo $info
* @param null $full
*
* @throws \Exception
* @return array|bool|float|int|mixed|null|number|string
*/
public static function validate($input, ValidationInfo $info, $full = null)
{
$html = Scope::get('Restler')->responseFormat instanceof HtmlFormat;
$name = $html ? "<strong>$info->label</strong>" : "`$info->name`";
try {
if (is_null($input)) {
if ($info->required) {
throw new RestException (400,
"$name is required.");
}
return null;
}
$error = isset ($info->message)
? $info->message
: "Invalid value specified for $name";
//if a validation method is specified
if (!empty($info->method)) {
$method = $info->method;
$info->method = '';
$r = self::validate($input, $info);
return $info->apiClassInstance->{$method} ($r);
}
// when type is an array check if it passes for any type
if (is_array($info->type)) {
//trace("types are ".print_r($info->type, true));
$types = $info->type;
foreach ($types as $type) {
$info->type = $type;
try {
$r = self::validate($input, $info);
if ($r !== false) {
return $r;
}
} catch (RestException $e) {
// just continue
}
}
throw new RestException (400, $error);
}
//patterns are supported only for non numeric types
if (isset ($info->pattern)
&& $info->type != 'int'
&& $info->type != 'float'
&& $info->type != 'number'
) {
if (!preg_match($info->pattern, $input)) {
throw new RestException (400, $error);
}
}
if (isset ($info->choice)) {
if (is_array($input)) {
foreach ($input as $i) {
if (!in_array($i, $info->choice)) {
$error .= ". Expected one of (" . implode(',', $info->choice) . ").";
throw new RestException (400, $error);
}
}
} elseif (!in_array($input, $info->choice)) {
$error .= ". Expected one of (" . implode(',', $info->choice) . ").";
throw new RestException (400, $error);
}
}
if (method_exists($class = get_called_class(), $info->type) && $info->type != 'validate') {
try {
return call_user_func("$class::$info->type", $input, $info);
} catch (Invalid $e) {
throw new RestException(400, $error . '. ' . $e->getMessage());
}
}
switch ($info->type) {
case 'int' :
case 'float' :
case 'number' :
if (!is_numeric($input)) {
$error .= '. Expecting '
. ($info->type == 'int' ? 'integer' : 'numeric')
. ' value';
break;
}
if ($info->type == 'int' && (int)$input != $input) {
if ($info->fix) {
$r = (int)$input;
} else {
$error .= '. Expecting integer value';
break;
}
} else {
$r = $info->numericValue($input);
}
if (isset ($info->min) && $r < $info->min) {
if ($info->fix) {
$r = $info->min;
} else {
$error .= ". Minimum required value is $info->min.";
break;
}
}
if (isset ($info->max) && $r > $info->max) {
if ($info->fix) {
$r = $info->max;
} else {
$error .= ". Maximum allowed value is $info->max.";
break;
}
}
return $r;
case 'string' :
if (!is_string($input)) {
$error .= '. Expecting alpha numeric value';
break;
}
if ($info->required && $input === '') {
$error = "$name is required.";
break;
}
$r = strlen($input);
if (isset ($info->min) && $r < $info->min) {
if ($info->fix) {
$input = str_pad($input, $info->min, $input);
} else {
$char = $info->min > 1 ? 'characters' : 'character';
$error .= ". Minimum $info->min $char required.";
break;
}
}
if (isset ($info->max) && $r > $info->max) {
if ($info->fix) {
$input = substr($input, 0, $info->max);
} else {
$char = $info->max > 1 ? 'characters' : 'character';
$error .= ". Maximum $info->max $char allowed.";
break;
}
}
return $input;
case 'bool':
case 'boolean':
if ($input === 'true' || $input === true) return true;
if (is_numeric($input)) return $input > 0;
return false;
case 'array':
if ($info->fix && is_string($input)) {
$input = explode(CommentParser::$arrayDelimiter, $input);
}
if (is_array($input)) {
$contentType =
Util::nestedValue($info, 'contentType') ? : null;
if ($info->fix) {
if ($contentType == 'indexed') {
$input = $info->filterArray($input, true);
} elseif ($contentType == 'associative') {
$input = $info->filterArray($input, true);
}
} elseif (
$contentType == 'indexed' &&
array_values($input) != $input
) {
$error .= '. Expecting a list of items but an item is given';
break;
} elseif (
$contentType == 'associative' &&
array_values($input) == $input &&
count($input)
) {
$error .= '. Expecting an item but a list is given';
break;
}
$r = count($input);
if (isset ($info->min) && $r < $info->min) {
$item = $info->max > 1 ? 'items' : 'item';
$error .= ". Minimum $info->min $item required.";
break;
}
if (isset ($info->max) && $r > $info->max) {
if ($info->fix) {
$input = array_slice($input, 0, $info->max);
} else {
$item = $info->max > 1 ? 'items' : 'item';
$error .= ". Maximum $info->max $item allowed.";
break;
}
}
if (
isset($contentType) &&
$contentType != 'associative' &&
$contentType != 'indexed'
) {
$name = $info->name;
$info->type = $contentType;
unset($info->contentType);
foreach ($input as $key => $chinput) {
$info->name = "{$name}[$key]";
$input[$key] = static::validate($chinput, $info);
}
}
return $input;
} elseif (isset($contentType)) {
$error .= '. Expecting items of type ' .
($html ? "<strong>$contentType</strong>" : "`$contentType`");
break;
}
break;
case 'mixed':
case 'unknown_type':
case 'unknown':
case null: //treat as unknown
return $input;
default :
if (!is_array($input)) {
break;
}
//do type conversion
if (class_exists($info->type)) {
$input = $info->filterArray($input, false);
$implements = class_implements($info->type);
if (
is_array($implements) &&
in_array('Luracast\\Restler\\Data\\iValueObject', $implements)
) {
return call_user_func(
"{$info->type}::__set_state", $input
);
}
$class = $info->type;
$instance = new $class();
if (is_array($info->children)) {
if (
empty($input) ||
!is_array($input) ||
$input === array_values($input)
) {
$error .= '. Expecting an item of type ' .
($html ? "<strong>$info->type</strong>" : "`$info->type`");
break;
}
foreach ($info->children as $key => $value) {
$cv = new ValidationInfo($value);
if (array_key_exists($key, $input) || $cv->required) {
$instance->{$key} = static::validate(
Util::nestedValue($input, $key),
$cv
);
}
}
}
return $instance;
}
}
throw new RestException (400, $error);
} catch (\Exception $e) {
static::$exceptions[] = $e;
if (static::$holdException) {
return null;
}
throw $e;
}
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Luracast\Restler\Data;
/**
* ValueObject base class, you may use this class to create your
* iValueObjects quickly
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ValueObject implements iValueObject
{
public function __toString()
{
return ' new ' . get_called_class() . '() ';
}
public static function __set_state(array $properties)
{
$class = get_called_class();
$instance = new $class ();
$vars = get_object_vars($instance);
foreach ($properties as $property => $value) {
if (property_exists($instance, $property)) {
// see if the property is accessible
if (array_key_exists($property, $vars)) {
$instance->{$property} = $value;
} else {
$method = 'set' . ucfirst($property);
if (method_exists($instance, $method)) {
call_user_func(array(
$instance,
$method
), $value);
}
}
}
}
return $instance;
}
public function __toArray()
{
$r = get_object_vars($this);
$methods = get_class_methods($this);
foreach ($methods as $m) {
if (substr($m, 0, 3) == 'get') {
$r [lcfirst(substr($m, 3))] = @$this->{$m} ();
}
}
return $r;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Luracast\Restler\Data;
/**
* Validation classes should implement this interface
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iValidate {
/**
* method used for validation.
*
* @param mixed $input
* data that needs to be validated
* @param ValidationInfo $info
* information to be used for validation
* @return boolean false in case of failure or fixed value in the expected
* type
* @throws \Luracast\Restler\RestException 400 with information about the
* failed
* validation
*/
public static function validate($input, ValidationInfo $info);
}

View File

@ -0,0 +1,39 @@
<?php
namespace Luracast\Restler\Data;
/**
* Restler is using many ValueObjects across to make it easy for the developers
* to use them with the help of code hinting etc.,
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iValueObject
{
/**
* This static method is called for creating an instance of the class by
* passing the initiation values as an array.
*
* @static
* @abstract
*
* @param array $properties
*
* @return iValueObject
*/
public static function __set_state(array $properties);
/**
* This method provides a string representation for the instance
*
* @return string
*/
public function __toString();
}

View File

@ -0,0 +1,360 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Data\Validator;
/**
* Static class to hold all restler defaults, change the values to suit your
* needs in the gateway file (index.php), you may also allow the api users to
* change them per request by adding the properties to Defaults::$overridables
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Defaults
{
// ==================================================================
//
// Class Mappings
//
// ------------------------------------------------------------------
/**
* @var string of name of the class that implements
* \Luracast\Restler\iCache the cache class to be used
*/
public static $cacheClass = 'Luracast\\Restler\\HumanReadableCache';
/**
* @var string full path of the directory where all the generated files will
* be kept. When set to null (default) it will use the cache folder that is
* in the same folder as index.php (gateway)
*/
public static $cacheDirectory;
/**
* @var string of name of the class that implements
* \Luracast\Restler\Data\iValidate the validator class to be used
*/
public static $validatorClass = 'Luracast\\Restler\\Data\\Validator';
/**
* @var string name of the class that implements \Luracast\Restler\iCompose
* the class to be used to compose the response
*/
public static $composeClass = 'Luracast\\Restler\\Compose';
// ==================================================================
//
// Routing
//
// ------------------------------------------------------------------
/**
* @var bool should auto routing for public and protected api methods
* should be enabled by default or not. Set this to false to get
* Restler 1.0 style behavior
*/
public static $autoRoutingEnabled = true;
/**
* @var boolean avoids creating multiple routes that can increase the
* ambiguity when set to true. when a method parameter is optional it is
* not mapped to the url and should only be used in request body or as
* query string `/resource?id=value`. When a parameter is required and is
* scalar, it will be mapped as part of the url `/resource/{id}`
*/
public static $smartAutoRouting = true;
/**
* @var boolean enables more ways of finding the parameter data in the request.
* If you need backward compatibility with Restler 2 or below turn this off
*/
public static $smartParameterParsing = true;
// ==================================================================
//
// API Version Management
//
// ------------------------------------------------------------------
/**
* @var null|string name that is used for vendor specific media type and
* api version using the Accept Header for example
* application/vnd.{vendor}-v1+json
*
* Keep this null if you do not want to use vendor MIME for specifying api version
*/
public static $apiVendor = null;
/**
* @var bool set it to true to force vendor specific MIME for versioning.
* It will be automatically set to true when Defaults::$vendor is not
* null and client is requesting for the custom MIME type
*/
public static $useVendorMIMEVersioning = false;
/**
* @var bool set it to true to use enableUrl based versioning
*/
public static $useUrlBasedVersioning = false;
// ==================================================================
//
// Request
//
// ------------------------------------------------------------------
/**
* @var string name to be used for the method parameter to capture the
* entire request data
*/
public static $fullRequestDataName = 'request_data';
/**
* @var string name of the property that can sent through $_GET or $_POST to
* override the http method of the request. Set it to null or
* blank string to disable http method override through request
* parameters.
*/
public static $httpMethodOverrideProperty = 'http_method';
/**
* @var bool should auto validating api parameters should be enabled by
* default or not. Set this to false to avoid validation.
*/
public static $autoValidationEnabled = true;
/**
* @var string name of the class that implements iUser interface to identify
* the user for caching purposes
*/
public static $userIdentifierClass = 'Luracast\\Restler\\User';
// ==================================================================
//
// Response
//
// ------------------------------------------------------------------
/**
* @var bool HTTP status codes are set on all responses by default.
* Some clients (like flash, mobile) have trouble dealing with non-200
* status codes on error responses.
*
* You can set it to true to force a HTTP 200 status code on all responses,
* even when errors occur. If you suppress status codes, look for an error
* response to determine if an error occurred.
*/
public static $suppressResponseCode = false;
public static $supportedCharsets = array('utf-8', 'iso-8859-1');
public static $supportedLanguages = array('en', 'en-US');
public static $charset = 'utf-8';
public static $language = 'en';
/**
* @var bool when set to true, it will exclude the response body
*/
public static $emptyBodyForNullResponse = true;
/**
* @var bool enables CORS support
*/
public static $crossOriginResourceSharing = false;
public static $accessControlAllowOrigin = '*';
public static $accessControlAllowMethods =
'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD';
// ==================================================================
//
// Header
//
// ------------------------------------------------------------------
/**
* @var array default Cache-Control template that used to set the
* Cache-Control header and has two values, first one is used when
* Defaults::$headerExpires is 0 and second one when it has some time
* value specified. When only one value is specified it will be used for
* both cases
*/
public static $headerCacheControl = array(
'no-cache, must-revalidate',
/* "public, " or "private, " will be prepended based on api method
* called (public or protected)
*/
'max-age={expires}, must-revalidate',
);
/**
* @var int sets the content to expire immediately when set to zero
* alternatively you can specify the number of seconds the content will
* expire. This setting can be altered at api level using php doc comment
* with @expires numOfSeconds
*/
public static $headerExpires = 0;
// ==================================================================
//
// Access Control
//
// ------------------------------------------------------------------
/**
* @var null|callable if the api methods are under access control mechanism
* you can attach a function here that returns true or false to determine
* visibility of a protected api method. this function will receive method
* info as the only parameter.
*/
public static $accessControlFunction = null;
/**
* @var int set the default api access mode
* value of 0 = public api
* value of 1 = hybrid api using `@access hybrid` comment
* value of 2 = protected api using `@access protected` comment
* value of 3 = protected api using `protected function` method
*/
public static $apiAccessLevel = 0;
/**
* @var string authentication method to be called in iAuthenticate
* Interface
*/
public static $authenticationMethod = '__isAllowed';
/**
* @var int time in milliseconds for bandwidth throttling,
* which is the minimum response time for each api request. You can
* change it per api method by setting `@throttle 3000` in php doc
* comment either at the method level or class level
*/
public static $throttle = 0;
// ==================================================================
//
// Overrides for API User
//
// ------------------------------------------------------------------
/**
* @var array use 'alternativeName'=> 'actualName' to set alternative
* names that can be used to represent the api method parameters and/or
* static properties of Defaults
*/
public static $aliases = array(
/**
* suppress_response_codes=true as an URL parameter to force
* a HTTP 200 status code on all responses
*/
'suppress_response_codes' => 'suppressResponseCode',
);
/**
* @var array determines the defaults that can be overridden by the api
* user by passing them as URL parameters
*/
public static $overridables = array(
'suppressResponseCode',
);
/**
* @var array contains validation details for defaults to be used when
* set through URL parameters
*/
public static $validation = array(
'suppressResponseCode' => array('type' => 'bool'),
'headerExpires' => array('type' => 'int', 'min' => 0),
);
// ==================================================================
//
// Overrides API Developer
//
// ------------------------------------------------------------------
/**
* @var array determines what are the phpdoc comment tags that will
* override the Defaults here with their values
*/
public static $fromComments = array(
/**
* use PHPDoc comments such as the following
* `
*
* @cache no-cache, must-revalidate` to set the Cache-Control header
* for a specific api method
*/
'cache' => 'headerCacheControl',
/**
* use PHPDoc comments such as the following
* `
*
* @expires 50` to set the Expires header
* for a specific api method
*/
'expires' => 'headerExpires',
/**
* use PHPDoc comments such as the following
* `
*
* @throttle 300`
* to set the bandwidth throttling for 300 milliseconds
* for a specific api method
*/
'throttle' => 'throttle',
/**
* enable or disable smart auto routing from method comments
* this one is hardwired so cant be turned off
* it is placed here just for documentation purpose
*/
'smart-auto-routing' => 'smartAutoRouting',
);
// ==================================================================
//
// Util
//
// ------------------------------------------------------------------
/**
* Use this method to set value to a static properly of Defaults when
* you want to make sure only proper values are taken in with the help of
* validation
*
* @static
*
* @param string $name name of the static property
* @param mixed $value value to set the property to
*
* @return bool
*/
public static function setProperty($name, $value)
{
if (!property_exists(__CLASS__, $name)) return false;
if (@is_array(Defaults::$validation[$name])) {
$info = new ValidationInfo(Defaults::$validation[$name]);
$value = Validator::validate($value, $info);
}
Defaults::$$name = $value;
return true;
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Luracast\Restler;
/**
* Static event broadcasting system for Restler
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
use Closure;
class EventDispatcher
{
private $listeners = array();
protected static $_waitList = array();
public static $self;
protected $events = array();
public function __construct() {
static::$self = $this;
if (!empty(static::$_waitList)) {
foreach (static::$_waitList as $param) {
call_user_func_array(array($this,$param[0]), $param[1]);
}
}
}
public static function __callStatic($eventName, $params)
{
if (0 === strpos($eventName, 'on')) {
if(static::$self){
return call_user_func_array(array(static::$self, $eventName), $params);
}
static::$_waitList[] = func_get_args();
return false;
}
}
public function __call($eventName, $params)
{
if (0 === strpos($eventName, 'on')) {
if (!@is_array($this->listeners[$eventName]))
$this->listeners[$eventName] = array();
$this->listeners[$eventName][] = $params[0];
}
return $this;
}
public static function addListener($eventName, Closure $callback)
{
return static::$eventName($callback);
}
public function on(array $eventHandlers)
{
for (
$count = count($eventHandlers),
$events = array_map(
'ucfirst',
$keys = array_keys(
$eventHandlers = array_change_key_case(
$eventHandlers,
CASE_LOWER
)
)
),
$i = 0;
$i < $count;
call_user_func(
array($this, "on{$events[$i]}"),
$eventHandlers[$keys[$i++]]
)
);
}
/**
* Fire an event to notify all listeners
*
* @param string $eventName name of the event
* @param array $params event related data
*/
protected function dispatch($eventName, array $params = array())
{
$this->events[] = $eventName;
$params = func_get_args();
$eventName = 'on'.ucfirst(array_shift($params));
if (isset($this->listeners[$eventName]))
foreach ($this->listeners[$eventName] as $callback)
call_user_func_array($callback, $params);
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace Luracast\Restler\Filter;
use Luracast\Restler\Defaults;
use Luracast\Restler\iFilter;
use Luracast\Restler\iUseAuthentication;
use Luracast\Restler\RestException;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class RateLimit implements iFilter, iUseAuthentication
{
/**
* @var \Luracast\Restler\Restler;
*/
public $restler;
/**
* @var int
*/
public static $usagePerUnit = 1200;
/**
* @var int
*/
public static $authenticatedUsagePerUnit = 5000;
/**
* @var string
*/
public static $unit = 'hour';
/**
* @var string group the current api belongs to
*/
public static $group = 'common';
protected static $units = array(
'second' => 1,
'minute' => 60,
'hour' => 3600, // 60*60 seconds
'day' => 86400, // 60*60*24 seconds
'week' => 604800, // 60*60*24*7 seconds
'month' => 2592000, // 60*60*24*30 seconds
);
/**
* @var array all paths beginning with any of the following will be excluded
* from documentation
*/
public static $excludedPaths = array('resources');
/**
* @param string $unit
* @param int $usagePerUnit
* @param int $authenticatedUsagePerUnit set it to false to give unlimited access
*
* @throws \InvalidArgumentException
* @return void
*/
public static function setLimit(
$unit, $usagePerUnit, $authenticatedUsagePerUnit = null
)
{
static::$unit = $unit;
static::$usagePerUnit = $usagePerUnit;
static::$authenticatedUsagePerUnit =
is_null($authenticatedUsagePerUnit) ? $usagePerUnit : $authenticatedUsagePerUnit;
}
public function __isAllowed()
{
if (static::$authenticatedUsagePerUnit
== static::$usagePerUnit
) return $this->check();
return null;
}
public function __setAuthenticationStatus($isAuthenticated = false)
{
header('X-Auth-Status: ' . ($isAuthenticated ? 'true' : 'false'));
$this->check($isAuthenticated);
}
private static function validate($unit)
{
if (!isset(static::$units[$unit]))
throw new \InvalidArgumentException(
'Rate Limit time unit should be '
. implode('|', array_keys(static::$units)) . '.'
);
}
private function check($isAuthenticated = false)
{
$path = $this->restler->url;
foreach (static::$excludedPaths as $exclude) {
if (empty($exclude) && empty($path)) {
return true;
} elseif (0 === strpos($path, $exclude)) {
return true;
}
}
static::validate(static::$unit);
$timeUnit = static::$units[static::$unit];
$maxPerUnit = $isAuthenticated
? static::$authenticatedUsagePerUnit
: static::$usagePerUnit;
if ($maxPerUnit) {
$user = Defaults::$userIdentifierClass;
if (!method_exists($user, 'getUniqueIdentifier')) {
throw new \UnexpectedValueException('`Defaults::$userIdentifierClass` must implement `iIdentifyUser` interface');
}
$id = "RateLimit_" . $maxPerUnit . '_per_' . static::$unit
. '_for_' . static::$group
. '_' . $user::getUniqueIdentifier();
$lastRequest = $this->restler->cache->get($id, true)
? : array('time' => 0, 'used' => 0);
$time = $lastRequest['time'];
$diff = time() - $time; # in seconds
$used = $lastRequest['used'];
header("X-RateLimit-Limit: $maxPerUnit per " . static::$unit);
if ($diff >= $timeUnit) {
$used = 1;
$time = time();
} elseif ($used >= $maxPerUnit) {
header("X-RateLimit-Remaining: 0");
$wait = $timeUnit - $diff;
sleep(1);
throw new RestException(429,
'Rate limit of ' . $maxPerUnit . ' request' .
($maxPerUnit > 1 ? 's' : '') . ' per '
. static::$unit . ' exceeded. Please wait for '
. static::duration($wait) . '.'
);
} else {
$used++;
}
$remainingPerUnit = $maxPerUnit - $used;
header("X-RateLimit-Remaining: $remainingPerUnit");
$this->restler->cache->set($id,
array('time' => $time, 'used' => $used));
}
return true;
}
private function duration($secs)
{
$units = array(
'week' => (int)($secs / 86400 / 7),
'day' => $secs / 86400 % 7,
'hour' => $secs / 3600 % 24,
'minute' => $secs / 60 % 60,
'second' => $secs % 60);
$ret = array();
//$unit = 'days';
foreach ($units as $k => $v) {
if ($v > 0) {
$ret[] = $v > 1 ? "$v {$k}s" : "$v $k";
//$unit = $k;
}
}
$i = count($ret) - 1;
if ($i) {
$ret[$i] = 'and ' . $ret[$i];
}
return implode(' ', $ret); //." $unit.";
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Format\HtmlFormat;
/**
* Storing and retrieving a message or array of key value pairs for one time use using $_SESSION
*
* They are typically used in view templates when using HtmlFormat
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Flash //implements \JsonSerializable
{
const SUCCESS = 'success';
const INFO = 'info';
const WARNING = 'warning';
const DANGER = 'danger';
/**
* @var Flash
*/
private static $instance;
private $usedOnce = false;
/**
* Flash a success message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function success($message, $header = '')
{
return static::message($message, $header, Flash::SUCCESS);
}
/**
* Flash a info message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function info($message, $header = '')
{
return static::message($message, $header, Flash::INFO);
}
/**
* Flash a warning message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function warning($message, $header = '')
{
return static::message($message, $header, Flash::WARNING);
}
/**
* Flash a error message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function danger($message, $header = '')
{
return static::message($message, $header, Flash::DANGER);
}
/**
* Flash a message to user
*
* @param string $text message text
* @param string $header
* @param string $type
*
* @return Flash
*/
public static function message($text, $header = '', $type = Flash::WARNING)
{
return static::set(array('message' => $text, 'header' => $header, 'type' => $type));
}
/**
* Set some data for one time use
*
* @param array $data array of key value pairs {@type associative}
*
* @return Flash
*/
public static function set(array $data)
{
if (!static::$instance)
static::$instance = new Flash();
if (!isset($_SESSION['flash']))
$_SESSION['flash'] = array();
$_SESSION['flash'] += $data;
HtmlFormat::$data['flash'] = static::$instance;
return static::$instance;
}
public function __get($name)
{
$this->usedOnce = true;
return Util::nestedValue($_SESSION, 'flash', $name);
}
public function __isset($name)
{
return !is_null(Util::nestedValue($_SESSION, 'flash', $name));
}
public function __destruct()
{
if ($this->usedOnce)
unset($_SESSION['flash']);
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
*/
public function jsonSerialize()
{
$this->usedOnce = true;
return isset($_SESSION['flash'])
? $_SESSION['flash']
: array();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Luracast\Restler\Format;
use ZendAmf\Parser\Amf3\Deserializer;
use ZendAmf\Parser\Amf3\Serializer;
use ZendAmf\Parser\InputStream;
use ZendAmf\Parser\OutputStream;
/**
* AMF Binary Format for Restler Framework.
* Native format supported by Adobe Flash and Adobe AIR
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class AmfFormat extends Format
{
const MIME = 'application/x-amf';
const EXTENSION = 'amf';
public function encode($data, $humanReadable = false)
{
$stream = new OutputStream();
$serializer = new Serializer($stream);
$serializer->writeTypeMarker($data);
return $stream->getStream();
}
public function decode($data)
{
$stream = new InputStream(substr($data, 1));
$deserializer = new Deserializer($stream);
return $deserializer->readTypeMarker();
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
/**
* Comma Separated Value Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class CsvFormat extends Format implements iDecodeStream
{
const MIME = 'text/csv';
const EXTENSION = 'csv';
public static $delimiter = ',';
public static $enclosure = '"';
public static $escape = '\\';
public static $haveHeaders = null;
/**
* Encode the given data in the csv format
*
* @param array $data
* resulting data that needs to
* be encoded in the given format
* @param boolean $humanReadable
* set to TRUE when restler
* is not running in production mode. Formatter has to
* make the encoded output more human readable
*
* @return string encoded string
*
* @throws RestException 500 on unsupported data
*/
public function encode($data, $humanReadable = false)
{
$char = Object::$separatorChar;
Object::$separatorChar = false;
$data = Object::toArray($data);
Object::$separatorChar = $char;
if (is_array($data) && array_values($data) == $data) {
//if indexed array
$lines = array();
$row = array_shift($data);
if (array_values($row) != $row) {
$lines[] = static::putRow(array_keys($row));
}
$lines[] = static::putRow(array_values($row));
foreach ($data as $row) {
$lines[] = static::putRow(array_values($row));
}
return implode(PHP_EOL, $lines) . PHP_EOL;
}
throw new RestException(
500,
'Unsupported data for ' . strtoupper(static::EXTENSION) . ' format'
);
}
protected static function putRow($data)
{
$fp = fopen('php://temp', 'r+');
fputcsv($fp, $data, static::$delimiter, static::$enclosure);
rewind($fp);
$data = fread($fp, 1048576);
fclose($fp);
return rtrim($data, PHP_EOL);
}
/**
* Decode the given data from the csv format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*/
public function decode($data)
{
$decoded = array();
if (empty($data)) {
return $decoded;
}
$lines = array_filter(explode(PHP_EOL, $data));
$keys = false;
$row = static::getRow(array_shift($lines));
if (is_null(static::$haveHeaders)) {
//try to guess with the given data
static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
}
static::$haveHeaders ? $keys = $row : $decoded[] = $row;
while (($row = static::getRow(array_shift($lines), $keys)) !== FALSE)
$decoded [] = $row;
$char = Object::$separatorChar;
Object::$separatorChar = false;
$decoded = Object::toArray($decoded);
Object::$separatorChar = $char;
return $decoded;
}
protected static function getRow($data, $keys = false)
{
if (empty($data)) {
return false;
}
$line = str_getcsv(
$data,
static::$delimiter,
static::$enclosure,
static::$escape
);
$row = array();
foreach ($line as $key => $value) {
if (is_numeric($value))
$value = floatval($value);
if ($keys) {
if (isset($keys [$key]))
$row [$keys [$key]] = $value;
} else {
$row [$key] = $value;
}
}
if ($keys) {
for ($i = count($row); $i < count($keys); $i++) {
$row[$keys[$i]] = null;
}
}
return $row;
}
/**
* Decode the given data stream
*
* @param string $stream A stream resource with data
* sent from client to the api
* in the given format.
*
* @return array associative array of the parsed data
*/
public function decodeStream($stream)
{
$decoded = array();
$keys = false;
$row = static::getRow(stream_get_line($stream, 0, PHP_EOL));
if (is_null(static::$haveHeaders)) {
//try to guess with the given data
static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
}
static::$haveHeaders ? $keys = $row : $decoded[] = $row;
while (($row = static::getRow(stream_get_line($stream, 0, PHP_EOL), $keys)) !== FALSE)
$decoded [] = $row;
$char = Object::$separatorChar;
Object::$separatorChar = false;
$decoded = Object::toArray($decoded);
Object::$separatorChar = $char;
return $decoded;
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace Luracast\Restler\Format;
/**
* Abstract class to implement common methods of iFormat
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
abstract class Format implements iFormat
{
/**
* override in the extending class
*/
const MIME = 'text/plain';
/**
* override in the extending class
*/
const EXTENSION = 'txt';
/**
* @var string charset encoding defaults to UTF8
*/
protected $charset='utf-8';
/**
* Injected at runtime
*
* @var \Luracast\Restler\Restler
*/
public $restler;
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
return array(
static::MIME => static::EXTENSION
);
}
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime)
{
//do nothing
}
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::MIME;
}
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension)
{
//do nothing;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::EXTENSION;
}
/**
* @return boolean is parsing the request supported?
*/
public function isReadable()
{
return true;
}
/**
* @return boolean is composing response supported?
*/
public function isWritable()
{
return true;
}
public function __toString()
{
return $this->getExtension();
}
}

View File

@ -0,0 +1,485 @@
<?php
namespace Luracast\Restler\Format;
use Exception;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Factory;
use Illuminate\View\FileViewFinder;
use Illuminate\View\View;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\Object;
use Luracast\Restler\Defaults;
use Luracast\Restler\RestException;
use Luracast\Restler\Restler;
use Luracast\Restler\Scope;
use Luracast\Restler\UI\Nav;
use Luracast\Restler\Util;
/**
* Html template format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class HtmlFormat extends Format
{
public static $mime = 'text/html';
public static $extension = 'html';
public static $view;
public static $errorView = 'debug.php';
public static $template = 'php';
public static $handleSession = true;
public static $useSmartViews = true;
/**
* @var null|string defaults to template named folder in Defaults::$cacheDirectory
*/
public static $cacheDirectory = null;
/**
* @var array global key value pair to be supplied to the templates. All
* keys added here will be available as a variable inside the template
*/
public static $data = array();
/**
* @var string set it to the location of your the view files. Defaults to
* views folder which is same level as vendor directory.
*/
public static $viewPath;
/**
* @var array template and its custom extension key value pair
*/
public static $customTemplateExtensions = array('blade' => 'blade.php');
/**
* @var bool used internally for error handling
*/
protected static $parseViewMetadata = true;
/**
* @var Restler;
*/
public $restler;
public function __construct()
{
//============ SESSION MANAGEMENT =============//
if (static::$handleSession) {
if (session_start() && isset($_SESSION['flash'])) {
static::$data['flash'] = $_SESSION['flash'];
unset($_SESSION['flash']);
}
}
if (!static::$viewPath) {
$array = explode('vendor', __DIR__, 2);
static::$viewPath = $array[0] . 'views';
}
}
public static function blade(array $data, $debug = true)
{
if (!class_exists('\Illuminate\View\View', true))
throw new RestException(500,
'Blade templates require laravel view classes to be installed using `composer install`');
$resolver = new EngineResolver();
$files = new Filesystem();
$compiler = new BladeCompiler($files, static::$cacheDirectory);
$engine = new CompilerEngine($compiler);
$resolver->register('blade', function () use ($engine) {
return $engine;
});
/** @var Restler $restler */
$restler = Scope::get('Restler');
//Lets expose shortcuts for our classes
spl_autoload_register(function ($className) use ($restler) {
if (isset($restler->apiMethodInfo->metadata['scope'][$className])) {
return class_alias($restler->apiMethodInfo->metadata['scope'][$className], $className);
}
if (isset(Scope::$classAliases[$className])) {
return class_alias(Scope::$classAliases[$className], $className);
}
return false;
}, true, true);
$viewFinder = new FileViewFinder($files, array(static::$viewPath));
$factory = new Factory($resolver, $viewFinder, new Dispatcher());
$path = $viewFinder->find(self::$view);
$view = new View($factory, $engine, self::$view, $path, $data);
$factory->callCreator($view);
return $view->render();
}
public static function twig(array $data, $debug = true)
{
if (!class_exists('\Twig_Environment', true))
throw new RestException(500,
'Twig templates require twig classes to be installed using `composer install`');
$loader = new \Twig_Loader_Filesystem(static::$viewPath);
$twig = new \Twig_Environment($loader, array(
'cache' => static::$cacheDirectory,
'debug' => $debug,
'use_strict_variables' => $debug,
));
if ($debug)
$twig->addExtension(new \Twig_Extension_Debug());
$twig->addFunction(
new \Twig_SimpleFunction(
'form',
'Luracast\Restler\UI\Forms::get',
array('is_safe' => array('html'))
)
);
$twig->addFunction(
new \Twig_SimpleFunction(
'form_key',
'Luracast\Restler\UI\Forms::key'
)
);
$twig->addFunction(
new \Twig_SimpleFunction(
'nav',
'Luracast\Restler\UI\Nav::get'
)
);
$twig->registerUndefinedFunctionCallback(function ($name) {
if (
isset(HtmlFormat::$data[$name]) &&
is_callable(HtmlFormat::$data[$name])
) {
return new \Twig_SimpleFunction(
$name,
HtmlFormat::$data[$name]
);
}
return false;
});
$template = $twig->loadTemplate(static::getViewFile());
return $template->render($data);
}
public static function handlebar(array $data, $debug = true)
{
return static::mustache($data, $debug);
}
public static function mustache(array $data, $debug = true)
{
if (!class_exists('\Mustache_Engine', true))
throw new RestException(
500,
'Mustache/Handlebar templates require mustache classes ' .
'to be installed using `composer install`'
);
if (!isset($data['nav']))
$data['nav'] = array_values(Nav::get());
$options = array(
'loader' => new \Mustache_Loader_FilesystemLoader(
static::$viewPath,
array('extension' => static::getViewExtension())
),
'helpers' => array(
'form' => function ($text, \Mustache_LambdaHelper $m) {
$params = explode(',', $m->render($text));
return call_user_func_array(
'Luracast\Restler\UI\Forms::get',
$params
);
},
)
);
if (!$debug)
$options['cache'] = static::$cacheDirectory;
$m = new \Mustache_Engine($options);
return $m->render(static::getViewFile(), $data);
}
public static function php(array $data, $debug = true)
{
if (static::$view == 'debug')
static::$viewPath = dirname(__DIR__) . '/views';
$view = static::getViewFile(true);
if (!is_readable($view)) {
throw new RestException(
500,
"view file `$view` is not readable. " .
'Check for file presence and file permissions'
);
}
$path = static::$viewPath . DIRECTORY_SEPARATOR;
$template = function ($view) use ($data, $path) {
$form = function () {
return call_user_func_array(
'Luracast\Restler\UI\Forms::get',
func_get_args()
);
};
if (!isset($data['form']))
$data['form'] = $form;
$nav = function () {
return call_user_func_array(
'Luracast\Restler\UI\Nav::get',
func_get_args()
);
};
if (!isset($data['nav']))
$data['nav'] = $nav;
$_ = function () use ($data, $path) {
extract($data);
$args = func_get_args();
$task = array_shift($args);
switch ($task) {
case 'require':
case 'include':
$file = $path . $args[0];
if (is_readable($file)) {
if (
isset($args[1]) &&
($arrays = Util::nestedValue($data, $args[1]))
) {
$str = '';
foreach ($arrays as $arr) {
extract($arr);
$str .= include $file;
}
return $str;
} else {
return include $file;
}
}
break;
case 'if':
if (count($args) < 2)
$args[1] = '';
if (count($args) < 3)
$args[2] = '';
return $args[0] ? $args[1] : $args[2];
break;
default:
if (isset($data[$task]) && is_callable($data[$task]))
return call_user_func_array($data[$task], $args);
}
return '';
};
extract($data);
return @include $view;
};
$value = $template($view);
if (is_string($value))
return $value;
}
/**
* Encode the given data in the format
*
* @param array $data resulting data that needs to
* be encoded in the given format
* @param boolean $humanReadable set to TRUE when restler
* is not running in production mode.
* Formatter has to make the encoded
* output more human readable
*
* @throws \Exception
* @return string encoded string
*/
public function encode($data, $humanReadable = false)
{
if (!is_readable(static::$viewPath)) {
throw new \Exception(
'The views directory `'
. self::$viewPath . '` should exist with read permission.'
);
}
static::$data['basePath'] = dirname($_SERVER['SCRIPT_NAME']);
static::$data['baseUrl'] = $this->restler->getBaseUrl();
static::$data['currentPath'] = $this->restler->url;
try {
$exception = $this->restler->exception;
$success = is_null($exception);
$error = $success ? null : $exception->getMessage();
$data = array(
'response' => Object::toArray($data),
'stages' => $this->restler->getEvents(),
'success' => $success,
'error' => $error
);
$info = $data['api'] = $this->restler->apiMethodInfo;
$metadata = Util::nestedValue(
$this->restler, 'apiMethodInfo', 'metadata'
);
$view = $success ? 'view' : 'errorView';
$value = false;
if (static::$parseViewMetadata && isset($metadata[$view])) {
if (is_array($metadata[$view])) {
self::$view = $metadata[$view]['description'];
$value = Util::nestedValue(
$metadata[$view], 'properties', 'value'
);
} else {
self::$view = $metadata[$view];
}
} elseif (!self::$view) {
$file = static::$viewPath . '/' . $this->restler->url . '.' . static::getViewExtension();
self::$view = static::$useSmartViews && is_readable($file)
? $this->restler->url
: static::$errorView;
}
if (
isset($metadata['param'])
&& (!$value || 0 === strpos($value, 'request'))
) {
$params = $metadata['param'];
foreach ($params as $index => &$param) {
$index = intval($index);
if (is_numeric($index)) {
$param['value'] = $this
->restler
->apiMethodInfo
->parameters[$index];
}
}
$data['request']['parameters'] = $params;
}
if ($value) {
$data = Util::nestedValue($data, explode('.', $value));
}
$data += static::$data;
if (false === ($i = strrpos(self::$view, '.'))) {
$template = self::$template;
} else {
self::$template = $template = substr(self::$view, $i + 1);
self::$view = substr(self::$view, 0, $i);
}
if (!static::$cacheDirectory) {
static::$cacheDirectory = Defaults::$cacheDirectory . DIRECTORY_SEPARATOR . $template;
if (!file_exists(static::$cacheDirectory)) {
if (!mkdir(static::$cacheDirectory)) {
throw new RestException(500, 'Unable to create cache directory `' . static::$cacheDirectory . '`');
}
}
}
if (method_exists($class = get_called_class(), $template)) {
return call_user_func("$class::$template", $data, $humanReadable);
}
throw new RestException(500, "Unsupported template system `$template`");
} catch (Exception $e) {
static::$parseViewMetadata = false;
$this->reset();
throw $e;
}
}
public static function getViewExtension()
{
return isset(static::$customTemplateExtensions[static::$template])
? static::$customTemplateExtensions[static::$template]
: static::$template;
}
public static function getViewFile($fullPath = false, $includeExtension = true)
{
$v = $fullPath ? static::$viewPath . '/' : '';
$v .= static::$view;
if ($includeExtension)
$v .= '.' . static::getViewExtension();
return $v;
}
private function reset()
{
static::$mime = 'text/html';
static::$extension = 'html';
static::$view = 'debug';
static::$template = 'php';
}
/**
* Decode the given data from the format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*
* @throws RestException
*/
public function decode($data)
{
throw new RestException(500, 'HtmlFormat is write only');
}
/**
* @return bool false as HTML format is write only
*/
public function isReadable()
{
return false;
}
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
return array(
static::$mime => static::$extension
);
}
/**
* Set the selected MIME type
*
* @param string $mime MIME type
*/
public function setMIME($mime)
{
static::$mime = $mime;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::$mime;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::$extension;
}
/**
* Set the selected file extension
*
* @param string $extension file extension
*/
public function setExtension($extension)
{
static::$extension = $extension;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Luracast\Restler\Format;
/**
* Javascript Object Notation Packaged in a method (JSONP)
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class JsFormat extends JsonFormat
{
const MIME = 'text/javascript';
const EXTENSION = 'js';
public static $callbackMethodName = 'parseResponse';
public static $callbackOverrideQueryString = 'callback';
public static $includeHeaders = true;
public function encode($data, $human_readable = false)
{
$r = array();
if (static::$includeHeaders) {
$r['meta'] = array();
foreach (headers_list() as $header) {
list($h, $v) = explode(': ', $header, 2);
$r['meta'][$h] = $v;
}
}
$r['data'] = $data;
if (isset($_GET[static::$callbackOverrideQueryString])) {
static::$callbackMethodName
= (string) $_GET[static::$callbackOverrideQueryString];
}
return static::$callbackMethodName . '('
. parent::encode($r, $human_readable) . ');';
}
public function isReadable()
{
return false;
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
/**
* Javascript Object Notation Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class JsonFormat extends Format
{
/**
* @var boolean|null shim for json_encode option JSON_PRETTY_PRINT set
* it to null to use smart defaults
*/
public static $prettyPrint = null;
/**
* @var boolean|null shim for json_encode option JSON_UNESCAPED_SLASHES
* set it to null to use smart defaults
*/
public static $unEscapedSlashes = null;
/**
* @var boolean|null shim for json_encode JSON_UNESCAPED_UNICODE set it
* to null to use smart defaults
*/
public static $unEscapedUnicode = null;
/**
* @var boolean|null shim for json_decode JSON_BIGINT_AS_STRING set it to
* null to
* use smart defaults
*/
public static $bigIntAsString = null;
const MIME = 'application/json';
const EXTENSION = 'json';
public function encode($data, $humanReadable = false)
{
if (!is_null(self::$prettyPrint)) {
$humanReadable = self::$prettyPrint;
}
if (is_null(self::$unEscapedSlashes)) {
self::$unEscapedSlashes = $humanReadable;
}
if (is_null(self::$unEscapedUnicode)) {
self::$unEscapedUnicode = $this->charset == 'utf-8';
}
$options = 0;
if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) // PHP >= 5.4
|| PHP_MAJOR_VERSION > 5 // PHP >= 6.0
) {
if ($humanReadable) $options |= JSON_PRETTY_PRINT;
if (self::$unEscapedSlashes) $options |= JSON_UNESCAPED_SLASHES;
if (self::$bigIntAsString) $options |= JSON_BIGINT_AS_STRING;
if (self::$unEscapedUnicode) $options |= JSON_UNESCAPED_UNICODE;
return json_encode(
Object::toArray($data, true), $options
);
}
$result = json_encode(Object::toArray($data, true));
if ($humanReadable) $result = $this->formatJson($result);
if (self::$unEscapedUnicode) {
$result = preg_replace_callback('/\\\u(\w\w\w\w)/',
function($matches)
{
if (function_exists('mb_convert_encoding'))
{
return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UTF-16BE');
}
else
{
return iconv('UTF-16BE','UTF-8',pack('H*', $matches[1]));
}
}
, $result);
}
if (self::$unEscapedSlashes) $result = str_replace('\/', '/', $result);
return $result;
}
public function decode($data)
{
$options = 0;
if (self::$bigIntAsString) {
if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) // PHP >= 5.4
|| PHP_MAJOR_VERSION > 5 // PHP >= 6.0
) {
$options |= JSON_BIGINT_AS_STRING;
} else {
$data = preg_replace(
'/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/',
': "$1"', $data
);
}
}
$decoded = json_decode($data, $options);
if (function_exists('json_last_error')) {
switch (json_last_error()) {
case JSON_ERROR_NONE :
return Object::toArray($decoded);
break;
case JSON_ERROR_DEPTH :
$message = 'maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH :
$message = 'underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR :
$message = 'unexpected control character found';
break;
case JSON_ERROR_SYNTAX :
$message = 'malformed JSON';
break;
case JSON_ERROR_UTF8 :
$message = 'malformed UTF-8 characters, possibly ' .
'incorrectly encoded';
break;
default :
$message = 'unknown error';
break;
}
throw new RestException (400, 'Error parsing JSON, ' . $message);
} elseif (strlen($data) && $decoded === null || $decoded === $data) {
throw new RestException (400, 'Error parsing JSON');
}
return Object::toArray($decoded);
}
/**
* Pretty print JSON string
*
* @param string $json
*
* @return string formatted json
*/
private function formatJson($json)
{
$tab = ' ';
$newJson = '';
$indentLevel = 0;
$inString = false;
$len = strlen($json);
for ($c = 0; $c < $len; $c++) {
$char = $json [$c];
switch ($char) {
case '{' :
case '[' :
if (!$inString) {
$newJson .= $char . "\n" .
str_repeat($tab, $indentLevel + 1);
$indentLevel++;
} else {
$newJson .= $char;
}
break;
case '}' :
case ']' :
if (!$inString) {
$indentLevel--;
$newJson .= "\n" .
str_repeat($tab, $indentLevel) . $char;
} else {
$newJson .= $char;
}
break;
case ',' :
if (!$inString) {
$newJson .= ",\n" .
str_repeat($tab, $indentLevel);
} else {
$newJson .= $char;
}
break;
case ':' :
if (!$inString) {
$newJson .= ': ';
} else {
$newJson .= $char;
}
break;
case '"' :
if ($c == 0) {
$inString = true;
} elseif ($c > 0 && $json [$c - 1] != '\\') {
$inString = !$inString;
}
default :
$newJson .= $char;
break;
}
}
return $newJson;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace Luracast\Restler\Format;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
abstract class MultiFormat implements iFormat
{
/**
* override in the extending class
*/
const MIME = 'text/plain,text/html';
/**
* override in the extending class
*/
const EXTENSION = 'txt,html';
/**
* @var string charset encoding defaults to UTF8
*/
protected $charset='utf-8';
public static $mime;
public static $extension;
/**
* Injected at runtime
*
* @var \Luracast\Restler\Restler
*/
public $restler;
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
$extensions = explode(',',static::EXTENSION);
$mimes = explode(',',static::MIME);
$count = max(count($extensions), count($mimes));
$extensions += array_fill(0, $count, end($extensions));
$mimes += array_fill(0, $count, end($mimes));
return array_combine($mimes,$extensions);
}
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime)
{
static::$mime = $mime;
}
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::$mime;
}
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension)
{
static::$extension = $extension;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::$extension;
}
/**
* @return boolean is parsing the request supported?
*/
public function isReadable()
{
return true;
}
/**
* @return boolean is composing response supported?
*/
public function isWritable()
{
return true;
}
public function __toString()
{
return $this->getExtension();
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use CFPropertyList\CFTypeDetector;
use CFPropertyList\CFPropertyList;
/**
* Plist Format for Restler Framework.
* Plist is the native data exchange format for Apple iOS and Mac platform.
* Use this format to talk to mac applications and iOS devices.
* This class is capable of serving both xml plist and binary plist.
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class PlistFormat extends MultiFormat
{
/**
* @var boolean set it to true binary plist is preferred
*/
public static $compact = null;
const MIME = 'application/xml,application/x-plist';
const EXTENSION = 'plist';
public function setMIME($mime)
{
static::$mime = $mime;
static::$compact = $mime == 'application/x-plist';
}
/**
* Encode the given data in plist format
*
* @param array $data
* resulting data that needs to
* be encoded in plist format
* @param boolean $humanReadable
* set to true when restler
* is not running in production mode. Formatter has to
* make the encoded output more human readable
*
* @return string encoded string
*/
public function encode($data, $humanReadable = false)
{
//require_once 'CFPropertyList.php';
if (!isset(self::$compact)) {
self::$compact = !$humanReadable;
}
/**
*
* @var CFPropertyList
*/
$plist = new CFPropertyList ();
$td = new CFTypeDetector ();
$guessedStructure = $td->toCFType(
Object::toArray($data)
);
$plist->add($guessedStructure);
return self::$compact
? $plist->toBinary()
: $plist->toXML(true);
}
/**
* Decode the given data from plist format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*/
public function decode($data)
{
//require_once 'CFPropertyList.php';
$plist = new CFPropertyList ();
$plist->parse($data);
return $plist->toArray();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Luracast\Restler\Format;
/**
* Tab Separated Value Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class TsvFormat extends CsvFormat
{
const MIME = 'text/csv';
const EXTENSION = 'csv';
public static $delimiter = "\t";
public static $enclosure = '"';
public static $escape = '\\';
public static $haveHeaders = null;
}

View File

@ -0,0 +1,145 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\RestException;
/**
* Support for Multi Part Form Data and File Uploads
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class UploadFormat extends Format
{
const MIME = 'multipart/form-data';
const EXTENSION = 'post';
public static $errors = array(
0 => false,
1 => "The uploaded file exceeds the maximum allowed size",
2 => "The uploaded file exceeds the maximum allowed size",
3 => "The uploaded file was only partially uploaded",
4 => "No file was uploaded",
6 => "Missing a temporary folder"
);
/**
* use it if you need to restrict uploads based on file type
* setting it as an empty array allows all file types
* default is to allow only png and jpeg images
*
* @var array
*/
public static $allowedMimeTypes = array('image/jpeg', 'image/png');
/**
* use it to restrict uploads based on file size
* set it to 0 to allow all sizes
* please note that it upload restrictions in the server
* takes precedence so it has to be lower than or equal to that
* default value is 1MB (1024x1024)bytes
* usual value for the server is 8388608
*
* @var int
*/
public static $maximumFileSize = 1048576;
/**
* Your own validation function for validating each uploaded file
* it can return false or throw an exception for invalid file
* use anonymous function / closure in PHP 5.3 and above
* use function name in other cases
*
* @var Callable
*/
public static $customValidationFunction;
/**
* Since exceptions are triggered way before at the `get` stage
*
* @var bool
*/
public static $suppressExceptionsAsError = false;
protected static function checkFile(& $file, $doMimeCheck = false, $doSizeCheck = false)
{
try {
if ($file['error']) {
//server is throwing an error
//assume that the error is due to maximum size limit
throw new RestException($file['error'] > 5 ? 500 : 413, static::$errors[$file['error']]);
}
$typeElements = explode('/', $file['type']);
$genericType = $typeElements[0].'/*';
if (
$doMimeCheck
&& !(
in_array($file['type'], self::$allowedMimeTypes)
|| in_array($genericType, self::$allowedMimeTypes)
)
) {
throw new RestException(403, "File type ({$file['type']}) is not supported.");
}
if ($doSizeCheck && $file['size'] > self::$maximumFileSize) {
throw new RestException(413, "Uploaded file ({$file['name']}) is too big.");
}
if (self::$customValidationFunction) {
if (!call_user_func(self::$customValidationFunction, $file)) {
throw new RestException(403, "File ({$file['name']}) is not supported.");
}
}
} catch (RestException $e) {
if (static::$suppressExceptionsAsError) {
$file['error'] = $e->getCode() == 413 ? 1 : 6;
$file['exception'] = $e;
} else {
throw $e;
}
}
}
public function encode($data, $humanReadable = false)
{
throw new RestException(500, 'UploadFormat is read only');
}
public function decode($data)
{
$doMimeCheck = !empty(self::$allowedMimeTypes);
$doSizeCheck = self::$maximumFileSize ? TRUE : FALSE;
//validate
foreach ($_FILES as & $file) {
if (is_array($file['error'])) {
foreach ($file['error'] as $i => $error) {
$innerFile = array();
foreach ($file as $property => $value) {
$innerFile[$property] = $value[$i];
}
if ($innerFile['name'])
static::checkFile($innerFile, $doMimeCheck, $doSizeCheck);
if (isset($innerFile['exception'])) {
$file['error'][$i] = $innerFile['error'];
$file['exception'] = $innerFile['exception'];
break;
}
}
} else {
if ($file['name'])
static::checkFile($file, $doMimeCheck, $doSizeCheck);
if (isset($innerFile['exception'])) {
break;
}
}
}
//sort file order if needed;
return UrlEncodedFormat::decoderTypeFix($_FILES + $_POST);
}
function isWritable()
{
return false;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Luracast\Restler\Format;
/**
* URL Encoded String Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class UrlEncodedFormat extends Format
{
const MIME = 'application/x-www-form-urlencoded';
const EXTENSION = 'post';
public function encode($data, $humanReadable = false)
{
return http_build_query(static::encoderTypeFix($data));
}
public function decode($data)
{
parse_str($data, $r);
return self::decoderTypeFix($r);
}
public static function encoderTypeFix(array $data)
{
foreach ($data as $k => $v) {
if (is_bool($v)) {
$data[$k] = $v = $v ? 'true' : 'false';
} elseif (is_array($v)) {
$data[$k] = $v = static::decoderTypeFix($v);
}
}
return $data;
}
public static function decoderTypeFix(array $data)
{
foreach ($data as $k => $v) {
if ($v === 'true' || $v === 'false') {
$data[$k] = $v = $v === 'true';
} elseif (is_array($v)) {
$data[$k] = $v = static::decoderTypeFix($v);
} elseif (empty($v) && $v != 0) {
unset($data[$k]);
}
}
return $data;
}
}

View File

@ -0,0 +1,348 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
use SimpleXMLElement;
use XMLWriter;
/**
* XML Markup Format for Restler Framework
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class XmlFormat extends Format
{
const MIME = 'application/xml';
const EXTENSION = 'xml';
// ==================================================================
//
// Properties related to reading/parsing/decoding xml
//
// ------------------------------------------------------------------
public static $importSettingsFromXml = false;
public static $parseAttributes = true;
public static $parseNamespaces = true;
public static $parseTextNodeAsProperty = true;
// ==================================================================
//
// Properties related to writing/encoding xml
//
// ------------------------------------------------------------------
public static $useTextNodeProperty = true;
public static $useNamespaces = true;
public static $cdataNames = array();
// ==================================================================
//
// Common Properties
//
// ------------------------------------------------------------------
public static $attributeNames = array();
public static $textNodeName = 'text';
public static $namespaces = array();
public static $namespacedProperties = array();
/**
* Default name for the root node.
*
* @var string $rootNodeName
*/
public static $rootName = 'response';
public static $defaultTagName = 'item';
/**
* When you decode an XML its structure is copied to the static vars
* we can use this function to echo them out and then copy paste inside
* our service methods
*
* @return string PHP source code to reproduce the configuration
*/
public static function exportCurrentSettings()
{
$s = 'XmlFormat::$rootName = "' . (self::$rootName) . "\";\n";
$s .= 'XmlFormat::$attributeNames = ' .
(var_export(self::$attributeNames, true)) . ";\n";
$s .= 'XmlFormat::$defaultTagName = "' .
self::$defaultTagName . "\";\n";
$s .= 'XmlFormat::$parseAttributes = ' .
(self::$parseAttributes ? 'true' : 'false') . ";\n";
$s .= 'XmlFormat::$parseNamespaces = ' .
(self::$parseNamespaces ? 'true' : 'false') . ";\n";
if (self::$parseNamespaces) {
$s .= 'XmlFormat::$namespaces = ' .
(var_export(self::$namespaces, true)) . ";\n";
$s .= 'XmlFormat::$namespacedProperties = ' .
(var_export(self::$namespacedProperties, true)) . ";\n";
}
return $s;
}
public function encode($data, $humanReadable = false)
{
$data = Object::toArray($data);
$xml = new XMLWriter();
$xml->openMemory();
$xml->startDocument('1.0', $this->charset);
if ($humanReadable) {
$xml->setIndent(true);
$xml->setIndentString(' ');
}
static::$useNamespaces && isset(static::$namespacedProperties[static::$rootName])
?
$xml->startElementNs(
static::$namespacedProperties[static::$rootName],
static::$rootName,
static::$namespaces[static::$namespacedProperties[static::$rootName]]
)
:
$xml->startElement(static::$rootName);
if (static::$useNamespaces) {
foreach (static::$namespaces as $prefix => $ns) {
if (isset(static::$namespacedProperties[static::$rootName])
&& static::$namespacedProperties[static::$rootName] == $prefix
)
continue;
$prefix = 'xmlns' . (empty($prefix) ? '' : ':' . $prefix);
$xml->writeAttribute($prefix, $ns);
}
}
$this->write($xml, $data, static::$rootName);
$xml->endElement();
return $xml->outputMemory();
}
public function write(XMLWriter $xml, $data, $parent)
{
$text = array();
if (is_array($data)) {
if (static::$useTextNodeProperty && isset($data[static::$textNodeName])) {
$text [] = $data[static::$textNodeName];
unset($data[static::$textNodeName]);
}
$attributes = array_flip(static::$attributeNames);
//make sure we deal with attributes first
$temp = array();
foreach ($data as $key => $value) {
if (isset($attributes[$key])) {
$temp[$key] = $data[$key];
unset($data[$key]);
}
}
$data = array_merge($temp, $data);
foreach ($data as $key => $value) {
if (is_numeric($key)) {
if (!is_array($value)) {
$text [] = $value;
continue;
}
$key = static::$defaultTagName;
}
$useNS = static::$useNamespaces
&& !empty(static::$namespacedProperties[$key])
&& false === strpos($key, ':');
if (is_array($value)) {
if ($value == array_values($value)) {
//numeric array, create siblings
foreach ($value as $v) {
$useNS
? $xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $v, $key);
$xml->endElement();
}
} else {
$useNS
? $xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $value, $key);
$xml->endElement();
}
continue;
} elseif (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
if (isset($attributes[$key])) {
$xml->writeAttribute($useNS ? static::$namespacedProperties[$key] . ':' . $key : $key, $value);
} else {
$useNS
?
$xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $value, $key);
$xml->endElement();
}
}
} else {
$text [] = (string)$data;
}
if (!empty($text)) {
if (count($text) == 1) {
in_array($parent, static::$cdataNames)
? $xml->writeCdata(implode('', $text))
: $xml->text(implode('', $text));
} else {
foreach ($text as $t) {
$xml->writeElement(static::$textNodeName, $t);
}
}
}
}
public function decode($data)
{
try {
if ($data == '') {
return array();
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($data,
"SimpleXMLElement", LIBXML_NOBLANKS | LIBXML_NOCDATA | LIBXML_COMPACT);
if (false === $xml) {
$error = libxml_get_last_error();
throw new RestException(400, 'Malformed XML. '
. trim($error->message, "\r\n") . ' at line ' . $error->line);
}
libxml_clear_errors();
if (static::$importSettingsFromXml) {
static::$attributeNames = array();
static::$namespacedProperties = array();
static::$namespaces = array();
static::$rootName = $xml->getName();
$namespaces = $xml->getNamespaces();
if (count($namespaces)) {
$p = strpos($data, $xml->getName());
if ($p && $data{$p - 1} == ':') {
$s = strpos($data, '<') + 1;
$prefix = substr($data, $s, $p - $s - 1);
static::$namespacedProperties[static::$rootName] = $prefix;
}
}
}
$data = $this->read($xml);
if (count($data) == 1 && isset($data[static::$textNodeName]))
$data = $data[static::$textNodeName];
return $data;
} catch (\RuntimeException $e) {
throw new RestException(400,
"Error decoding request. " . $e->getMessage());
}
}
public function read(SimpleXMLElement $xml, $namespaces = null)
{
$r = array();
$text = (string)$xml;
if (static::$parseAttributes) {
$attributes = $xml->attributes();
foreach ($attributes as $key => $value) {
if (static::$importSettingsFromXml
&& !in_array($key, static::$attributeNames)
) {
static::$attributeNames[] = $key;
}
$r[$key] = static::setType((string)$value);
}
}
$children = $xml->children();
foreach ($children as $key => $value) {
if (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
$r[$key] = array($r[$key]);
} else {
$r[$key] = array($r[$key]);
}
$r[$key][] = $this->read($value, $namespaces);
} else {
$r[$key] = $this->read($value);
}
}
if (static::$parseNamespaces) {
if (is_null($namespaces))
$namespaces = $xml->getDocNamespaces(true);
foreach ($namespaces as $prefix => $ns) {
static::$namespaces[$prefix] = $ns;
if (static::$parseAttributes) {
$attributes = $xml->attributes($ns);
foreach ($attributes as $key => $value) {
if (isset($r[$key])) {
$key = "{$prefix}:$key";
}
if (static::$importSettingsFromXml
&& !in_array($key, static::$attributeNames)
) {
static::$namespacedProperties[$key] = $prefix;
static::$attributeNames[] = $key;
}
$r[$key] = static::setType((string)$value);
}
}
$children = $xml->children($ns);
foreach ($children as $key => $value) {
if (static::$importSettingsFromXml)
static::$namespacedProperties[$key] = $prefix;
if (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
$r[$key] = array($r[$key]);
} else {
$r[$key] = array($r[$key]);
}
$r[$key][] = $this->read($value, $namespaces);
} else {
$r[$key] = $this->read($value, $namespaces);
}
}
}
}
if (empty($text) && $text !== '0') {
if (empty($r)) return null;
} else {
empty($r)
? $r = static::setType($text)
: (
static::$parseTextNodeAsProperty
? $r[static::$textNodeName] = static::setType($text)
: $r[] = static::setType($text)
);
}
return $r;
}
public static function setType($value)
{
if (empty($value) && $value !== '0')
return null;
if ($value == 'true')
return true;
if ($value == 'false')
return true;
return $value;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Luracast\Restler\Format;
use Symfony\Component\Yaml\Yaml;
use Luracast\Restler\Data\Object;
/**
* YAML Format for Restler Framework
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class YamlFormat extends Format
{
const MIME = 'text/plain';
const EXTENSION = 'yaml';
public function encode($data, $humanReadable = false)
{
// require_once 'sfyaml.php';
return @Yaml::dump(Object::toArray($data));
}
public function decode($data)
{
// require_once 'sfyaml.php';
return Yaml::parse($data);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Luracast\Restler\Format;
/**
* Interface for creating formats that accept steams for decoding
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iDecodeStream
{
/**
* Decode the given data stream
*
* @param string $stream A stream resource with data
* sent from client to the api
* in the given format.
*
* @return array associative array of the parsed data
*/
public function decodeStream($stream);
}

View File

@ -0,0 +1,109 @@
<?php
namespace Luracast\Restler\Format;
/**
* Interface for creating custom data formats
* like xml, json, yaml, amf etc
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iFormat
{
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap();
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime);
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset);
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset();
/**
* Get selected MIME type
*/
public function getMIME();
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension);
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension();
/**
* Encode the given data in the format
*
* @param array $data
* resulting data that needs to
* be encoded in the given format
* @param boolean $humanReadable
* set to TRUE when restler
* is not running in production mode. Formatter has to
* make the encoded output more human readable
* @return string encoded string
*/
public function encode($data, $humanReadable = false);
/**
* Decode the given data from the format
*
* @param string $data
* data sent from client to
* the api in the given format.
* @return array associative array of the parsed data
*/
public function decode($data);
/**
* @return boolean is parsing the request supported?
*/
public function isReadable();
/**
* @return boolean is composing response supported?
*/
public function isWritable();
}

View File

@ -0,0 +1,129 @@
<?php
namespace Luracast\Restler;
/**
* Default Cache that writes/reads human readable files for caching purpose
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class HumanReadableCache implements iCache
{
/**
* @var string path of the folder to hold cache files
*/
public static $cacheDir;
public function __construct()
{
if (is_null(self::$cacheDir)) {
self::$cacheDir = Defaults::$cacheDirectory;
}
}
/**
* store data in the cache
*
* @param string $name
* @param mixed $data
*
* @throws \Exception
* @return boolean true if successful
*/
public function set($name, $data)
{
if (is_array($data)) {
$s = '$o = array();' . PHP_EOL . PHP_EOL;
$s .= '// ** THIS IS AN AUTO GENERATED FILE.'
. ' DO NOT EDIT MANUALLY ** ';
foreach ($data as $key => $value) {
$s .= PHP_EOL . PHP_EOL .
"//==================== $key ===================="
. PHP_EOL . PHP_EOL;
if (is_array($value)) {
$s .= '$o[\'' . $key . '\'] = array();';
foreach ($value as $ke => $va) {
$s .= PHP_EOL . PHP_EOL . "//==== $key $ke ===="
. PHP_EOL . PHP_EOL;
$s .= '$o[\'' . $key . '\'][\'' . $ke . '\'] = ' .
str_replace(' ', ' ',
var_export($va, true)) . ';';
}
} else {
$s .= '$o[\'' . $key . '\'] = '
. var_export($value, true) . ';';
}
}
$s .= PHP_EOL . 'return $o;';
} else {
$s = 'return ' . var_export($data, true) . ';';
}
$file = $this->_file($name);
$r = @file_put_contents($file, "<?php $s");
@chmod($file, 0777);
if ($r === false) {
$this->throwException();
}
return $r;
}
/**
* retrieve data from the cache
*
* @param string $name
* @param bool $ignoreErrors
*
* @return mixed
*/
public function get($name, $ignoreErrors = false)
{
$file = $this->_file($name);
if (file_exists($file)) {
return include($file);
}
}
/**
* delete data from the cache
*
* @param string $name
* @param bool $ignoreErrors
*
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false)
{
return @unlink($this->_file($name));
}
/**
* check if the given name is cached
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name)
{
return file_exists($this->_file($name));
}
private function _file($name)
{
return self::$cacheDir . '/' . $name . '.php';
}
private function throwException()
{
throw new \Exception(
'The cache directory `'
. self::$cacheDir . '` should exist with write permission.'
);
}
}

View File

@ -0,0 +1,8 @@
Luracast Restler Framework
==========================
Restler is a simple and effective multi-format Web API Server written in PHP.
This repository contains just the framework files for installing the framework core using composer require statements.
For more information, usage examples, pull requests, and issues go to the [Main Repository](https://github.com/Luracast/Restler)

View File

@ -0,0 +1,53 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Format\JsonFormat;
/**
* Static class for handling redirection
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Redirect
{
/**
* Redirect to given url
*
* @param string $url relative path or full url
* @param array $params associative array of query parameters
* @param array $flashData associative array of properties to be set in $_SESSION for one time use
* @param int $status http status code to send the response with ideally 301 or 302
*
* @return array
*/
public static function to($url, array $params = array(), array $flashData = array(), $status = 302)
{
$url = ltrim($url, '/');
/** @var $r Restler */
$r = Scope::get('Restler');
$base = $r->getBaseUrl() . '/';
if (0 !== strpos($url, 'http'))
$url = $base . $url;
if (!empty($flashData) || $base . $r->url !== $url || Util::getRequestMethod() != 'GET') {
if ($r->responseFormat instanceof JsonFormat)
return array('redirect' => $url);
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
Flash::set($flashData);
header(
"{$_SERVER['SERVER_PROTOCOL']} $status " .
(isset(RestException::$codes[$status]) ? RestException::$codes[$status] : '')
);
header("Location: $url");
die('');
}
return array();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Special Exception for raising API errors
* that can be used in API methods
*
* @category Framework
* @package Restler
* @subpackage exception
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class RestException extends Exception
{
/**
* HTTP status codes
*
* @var array
*/
public static $codes = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => '(Unused)',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
429 => 'Too Many Requests', //still in draft but used for rate limiting
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
);
private $details;
private $stage;
/**
* @param string $httpStatusCode http status code
* @param string|null $errorMessage error message
* @param array $details any extra detail about the exception
* @param Exception $previous previous exception if any
*/
public function __construct($httpStatusCode, $errorMessage = null, array $details = array(), Exception $previous = null)
{
$events = Scope::get('Restler')->getEvents();
if(count($events)<= 1){
$this->stage = 'setup';
} else {
$this->stage = $previous ? $events[count($events)-2] : end($events);
}
$this->details = $details;
parent::__construct($errorMessage, $httpStatusCode, $previous);
}
/**
* Get extra details about the exception
*
* @return array details array
*/
public function getDetails()
{
return $this->details;
}
public function getStage()
{
return $this->stage;
}
public function getStages()
{
$e = Scope::get('Restler')->getEvents();
$i = array_search($this->stage, $e);
return array(
'success' => array_slice($e, 0, $i),
'failure' => array_slice($e, $i),
);
}
public function getErrorMessage()
{
$statusCode = $this->getCode();
$message = $this->getMessage();
if (isset(RestException::$codes[$statusCode])) {
$message = RestException::$codes[$statusCode] .
(empty($message) ? '' : ': ' . $message);
}
return $message;
}
public function getSource()
{
$e = $this;
while ($e->getPrevious()) {
$e = $e->getPrevious();
}
return basename($e->getFile()) . ':'
. $e->getLine() . ' at '
. $this->getStage() . ' stage';
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,696 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Exception;
/**
* Router class that routes the urls to api methods along with parameters
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Routes
{
public static $prefixingParameterNames = array(
'id'
);
protected static $routes = array();
protected static $models = array();
/**
* Route the public and protected methods of an Api class
*
* @param string $className
* @param string $resourcePath
* @param int $version
*
* @throws RestException
*/
public static function addAPIClass($className, $resourcePath = '', $version = 1)
{
/*
* Mapping Rules
* =============
*
* - Optional parameters should not be mapped to URL
* - If a required parameter is of primitive type
* - If one of the self::$prefixingParameterNames
* - Map it to URL
* - Else If request method is POST/PUT/PATCH
* - Map it to body
* - Else If request method is GET/DELETE
* - Map it to body
* - If a required parameter is not primitive type
* - Do not include it in URL
*/
$class = new ReflectionClass($className);
try {
$classMetadata = CommentParser::parse($class->getDocComment());
} catch (Exception $e) {
throw new RestException(500, "Error while parsing comments of `$className` class. " . $e->getMessage());
}
$classMetadata['scope'] = $scope = static::scope($class);
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC +
ReflectionMethod::IS_PROTECTED);
foreach ($methods as $method) {
$methodUrl = strtolower($method->getName());
//method name should not begin with _
if ($methodUrl{0} == '_') {
continue;
}
$doc = $method->getDocComment();
try {
$metadata = CommentParser::parse($doc) + $classMetadata;
} catch (Exception $e) {
throw new RestException(500, "Error while parsing comments of `{$className}::{$method->getName()}` method. " . $e->getMessage());
}
//@access should not be private
if (isset($metadata['access'])
&& $metadata['access'] == 'private'
) {
continue;
}
$arguments = array();
$defaults = array();
$params = $method->getParameters();
$position = 0;
$pathParams = array();
$allowAmbiguity
= (isset($metadata['smart-auto-routing'])
&& $metadata['smart-auto-routing'] != 'true')
|| !Defaults::$smartAutoRouting;
$metadata['resourcePath'] = $resourcePath;
if (isset($classMetadata['description'])) {
$metadata['classDescription'] = $classMetadata['description'];
}
if (isset($classMetadata['classLongDescription'])) {
$metadata['classLongDescription']
= $classMetadata['longDescription'];
}
if (!isset($metadata['param'])) {
$metadata['param'] = array();
}
if (isset($metadata['return']['type'])) {
if ($qualified = Scope::resolve($metadata['return']['type'], $scope))
list($metadata['return']['type'], $metadata['return']['children']) =
static::getTypeAndModel(new ReflectionClass($qualified), $scope);
} else {
//assume return type is array
$metadata['return']['type'] = 'array';
}
foreach ($params as $param) {
$children = array();
$type =
$param->isArray() ? 'array' : $param->getClass();
$arguments[$param->getName()] = $position;
$defaults[$position] = $param->isDefaultValueAvailable() ?
$param->getDefaultValue() : null;
if (!isset($metadata['param'][$position])) {
$metadata['param'][$position] = array();
}
$m = & $metadata ['param'] [$position];
$m ['name'] = $param->getName();
if (empty($m['label']))
$m['label'] = static::label($m['name']);
if (is_null($type) && isset($m['type'])) {
$type = $m['type'];
}
if ($m['name'] == 'email' && empty($m[CommentParser::$embeddedDataName]['type']) && $type == 'string')
$m[CommentParser::$embeddedDataName]['type'] = 'email';
$m ['default'] = $defaults [$position];
$m ['required'] = !$param->isOptional();
$contentType = Util::nestedValue(
$m,
CommentParser::$embeddedDataName,
'type'
);
if ($contentType && $qualified = Scope::resolve($contentType, $scope)) {
list($m[CommentParser::$embeddedDataName]['type'], $children) = static::getTypeAndModel(
new ReflectionClass($qualified), $scope
);
}
if ($type instanceof ReflectionClass) {
list($type, $children) = static::getTypeAndModel($type, $scope);
} elseif ($type && is_string($type) && $qualified = Scope::resolve($type, $scope)) {
list($type, $children)
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
}
if (isset($type)) {
$m['type'] = $type;
}
$m['children'] = $children;
if ($m['name'] == Defaults::$fullRequestDataName) {
$from = 'body';
if (!isset($m['type'])) {
$type = $m['type'] = 'array';
}
} elseif (isset($m[CommentParser::$embeddedDataName]['from'])) {
$from = $m[CommentParser::$embeddedDataName]['from'];
} else {
if ((isset($type) && Util::isObjectOrArray($type))
) {
$from = 'body';
if (!isset($type)) {
$type = $m['type'] = 'array';
}
} elseif ($m['required'] && in_array($m['name'], static::$prefixingParameterNames)) {
$from = 'path';
} else {
$from = 'body';
}
}
$m[CommentParser::$embeddedDataName]['from'] = $from;
if (!isset($m['type'])) {
$type = $m['type'] = static::type($defaults[$position]);
}
if ($allowAmbiguity || $from == 'path') {
$pathParams [] = $position;
}
$position++;
}
$accessLevel = 0;
if ($method->isProtected()) {
$accessLevel = 3;
} elseif (isset($metadata['access'])) {
if ($metadata['access'] == 'protected') {
$accessLevel = 2;
} elseif ($metadata['access'] == 'hybrid') {
$accessLevel = 1;
}
} elseif (isset($metadata['protected'])) {
$accessLevel = 2;
}
/*
echo " access level $accessLevel for $className::"
.$method->getName().$method->isProtected().PHP_EOL;
*/
// take note of the order
$call = array(
'url' => null,
'className' => $className,
'path' => rtrim($resourcePath, '/'),
'methodName' => $method->getName(),
'arguments' => $arguments,
'defaults' => $defaults,
'metadata' => $metadata,
'accessLevel' => $accessLevel,
);
// if manual route
if (preg_match_all(
'/@url\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)'
. '[ \t]*\/?(\S*)/s',
$doc, $matches, PREG_SET_ORDER
)
) {
foreach ($matches as $match) {
$httpMethod = $match[1];
$url = rtrim($resourcePath . $match[2], '/');
//deep copy the call, as it may change for each @url
$copy = unserialize(serialize($call));
foreach ($copy['metadata']['param'] as $i => $p) {
$inPath =
strpos($url, '{' . $p['name'] . '}') ||
strpos($url, ':' . $p['name']);
if ($inPath) {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'path';
} elseif ($httpMethod == 'GET' || $httpMethod == 'DELETE') {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'query';
} elseif ($p[CommentParser::$embeddedDataName]['from'] == 'path') {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'body';
}
}
$url = preg_replace_callback('/{[^}]+}|:[^\/]+/',
function ($matches) use ($call) {
$match = trim($matches[0], '{}:');
$index = $call['arguments'][$match];
return '{' .
Routes::typeChar(isset(
$call['metadata']['param'][$index]['type'])
? $call['metadata']['param'][$index]['type']
: null)
. $index . '}';
}, $url);
static::addPath($url, $copy, $httpMethod, $version);
}
//if auto route enabled, do so
} elseif (Defaults::$autoRoutingEnabled) {
// no configuration found so use convention
if (preg_match_all(
'/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i',
$methodUrl, $matches)
) {
$httpMethod = strtoupper($matches[0][0]);
$methodUrl = substr($methodUrl, strlen($httpMethod));
} else {
$httpMethod = 'GET';
}
if ($methodUrl == 'index') {
$methodUrl = '';
}
$url = empty($methodUrl) ? rtrim($resourcePath, '/')
: $resourcePath . $methodUrl;
$lastPathParam = array_keys($pathParams);
$lastPathParam = end($lastPathParam);
for ($position = 0; $position < count($params); $position++) {
$from = $metadata['param'][$position][CommentParser::$embeddedDataName]['from'];
if ($from == 'body' && ($httpMethod == 'GET' ||
$httpMethod == 'DELETE')
) {
$call['metadata']['param'][$position][CommentParser::$embeddedDataName]['from']
= 'query';
}
}
if (empty($pathParams) || $allowAmbiguity) {
static::addPath($url, $call, $httpMethod, $version);
}
foreach ($pathParams as $position) {
if (!empty($url))
$url .= '/';
$url .= '{' .
static::typeChar(isset($call['metadata']['param'][$position]['type'])
? $call['metadata']['param'][$position]['type']
: null)
. $position . '}';
if ($allowAmbiguity || $position == $lastPathParam) {
static::addPath($url, $call, $httpMethod, $version);
}
}
}
}
}
/**
* @access private
*/
public static function typeChar($type = null)
{
if (!$type) {
return 's';
}
switch ($type{0}) {
case 'i':
case 'f':
return 'n';
}
return 's';
}
protected static function addPath($path, array $call,
$httpMethod = 'GET', $version = 1)
{
$call['url'] = preg_replace_callback(
"/\{\S(\d+)\}/",
function ($matches) use ($call) {
return '{' .
$call['metadata']['param'][$matches[1]]['name'] . '}';
},
$path
);
//check for wildcard routes
if (substr($path, -1, 1) == '*') {
$path = rtrim($path, '/*');
static::$routes["v$version"]['*'][$path][$httpMethod] = $call;
} else {
static::$routes["v$version"][$path][$httpMethod] = $call;
//create an alias with index if the method name is index
if ($call['methodName'] == 'index')
static::$routes["v$version"][ltrim("$path/index", '/')][$httpMethod] = $call;
}
}
/**
* Find the api method for the given url and http method
*
* @param string $path Requested url path
* @param string $httpMethod GET|POST|PUT|PATCH|DELETE etc
* @param int $version Api Version number
* @param array $data Data collected from the request
*
* @throws RestException
* @return ApiMethodInfo
*/
public static function find($path, $httpMethod,
$version = 1, array $data = array())
{
$p = Util::nestedValue(static::$routes, "v$version");
if (!$p) {
throw new RestException(
404,
$version == 1 ? '' : "Version $version is not supported"
);
}
$status = 404;
$message = null;
$methods = array();
if (isset($p[$path][$httpMethod])) {
//================== static routes ==========================
return static::populate($p[$path][$httpMethod], $data);
} elseif (isset($p['*'])) {
//================== wildcard routes ========================
uksort($p['*'], function ($a, $b) {
return strlen($b) - strlen($a);
});
foreach ($p['*'] as $key => $value) {
if (strpos($path, $key) === 0 && isset($value[$httpMethod])) {
//path found, convert rest of the path to parameters
$path = substr($path, strlen($key) + 1);
$call = ApiMethodInfo::__set_state($value[$httpMethod]);
$call->parameters = empty($path)
? array()
: explode('/', $path);
return $call;
}
}
}
//================== dynamic routes =============================
//add newline char if trailing slash is found
if (substr($path, -1) == '/')
$path .= PHP_EOL;
//if double slash is found fill in newline char;
$path = str_replace('//', '/' . PHP_EOL . '/', $path);
ksort($p);
foreach ($p as $key => $value) {
if (!isset($value[$httpMethod])) {
continue;
}
$regex = str_replace(array('{', '}'),
array('(?P<', '>[^/]+)'), $key);
if (preg_match_all(":^$regex$:i", $path, $matches, PREG_SET_ORDER)) {
$matches = $matches[0];
$found = true;
foreach ($matches as $k => $v) {
if (is_numeric($k)) {
unset($matches[$k]);
continue;
}
$index = intval(substr($k, 1));
$details = $value[$httpMethod]['metadata']['param'][$index];
if ($k{0} == 's' || strpos($k, static::pathVarTypeOf($v)) === 0) {
//remove the newlines
$data[$details['name']] = trim($v, PHP_EOL);
} else {
$status = 400;
$message = 'invalid value specified for `'
. $details['name'] . '`';
$found = false;
break;
}
}
if ($found) {
return static::populate($value[$httpMethod], $data);
}
}
}
if ($status == 404) {
//check if other methods are allowed
if (isset($p[$path])) {
$status = 405;
$methods = array_keys($p[$path]);
}
}
if ($status == 405) {
header('Allow: ' . implode(', ', $methods));
}
throw new RestException($status, $message);
}
/**
* Populates the parameter values
*
* @param array $call
* @param $data
*
* @return ApiMethodInfo
*
* @access private
*/
protected static function populate(array $call, $data)
{
$call['parameters'] = $call['defaults'];
$p = & $call['parameters'];
foreach ($data as $key => $value) {
if (isset($call['arguments'][$key])) {
$p[$call['arguments'][$key]] = $value;
}
}
if (Defaults::$smartParameterParsing && 'post' != (string)Util::$restler->requestFormat) {
if (
count($p) == 1 &&
($m = Util::nestedValue($call, 'metadata', 'param', 0)) &&
!array_key_exists($m['name'], $data) &&
array_key_exists(Defaults::$fullRequestDataName, $data) &&
!is_null($d = $data[Defaults::$fullRequestDataName]) &&
isset($m['type']) &&
static::typeMatch($m['type'], $d)
) {
$p[0] = $d;
} else {
$bodyParamCount = 0;
$lastBodyParamIndex = -1;
$lastM = null;
foreach ($call['metadata']['param'] as $k => $m) {
if ($m[CommentParser::$embeddedDataName]['from'] == 'body') {
$bodyParamCount++;
$lastBodyParamIndex = $k;
$lastM = $m;
}
}
if (
$bodyParamCount == 1 &&
!array_key_exists($lastM['name'], $data) &&
array_key_exists(Defaults::$fullRequestDataName, $data) &&
!is_null($d = $data[Defaults::$fullRequestDataName])
) {
$p[$lastBodyParamIndex] = $d;
}
}
}
$r = ApiMethodInfo::__set_state($call);
$modifier = "_modify_{$r->methodName}_api";
if (method_exists($r->className, $modifier)) {
$stage = end(Scope::get('Restler')->getEvents());
if (empty($stage))
$stage = 'setup';
$r = Scope::get($r->className)->$modifier($r, $stage) ? : $r;
}
return $r;
}
/**
* @access private
*/
protected static function pathVarTypeOf($var)
{
if (is_numeric($var)) {
return 'n';
}
if ($var === 'true' || $var === 'false') {
return 'b';
}
return 's';
}
protected static function typeMatch($type, $var)
{
switch ($type) {
case 'boolean':
case 'bool':
return is_bool($var);
case 'array':
case 'object':
return is_array($var);
case 'string':
case 'int':
case 'integer':
case 'float':
case 'number':
return is_scalar($var);
}
return true;
}
/**
* Get the type and associated model
*
* @param ReflectionClass $class
* @param array $scope
*
* @throws RestException
* @throws \Exception
* @return array
*
* @access protected
*/
protected static function getTypeAndModel(ReflectionClass $class, array $scope)
{
$className = $class->getName();
if (isset(static::$models[$className])) {
return static::$models[$className];
}
$children = array();
try {
$props = $class->getProperties(ReflectionProperty::IS_PUBLIC);
foreach ($props as $prop) {
$name = $prop->getName();
$child = array('name' => $name);
if ($c = $prop->getDocComment()) {
$child += Util::nestedValue(CommentParser::parse($c), 'var');
} else {
$o = $class->newInstance();
$p = $prop->getValue($o);
if (is_object($p)) {
$child['type'] = get_class($p);
} elseif (is_array($p)) {
$child['type'] = 'array';
if (count($p)) {
$pc = reset($p);
if (is_object($pc)) {
$child['contentType'] = get_class($pc);
}
}
}
}
$child += array(
'type' => $child['name'] == 'email' ? 'email' : 'string',
'label' => static::label($child['name'])
);
isset($child[CommentParser::$embeddedDataName])
? $child[CommentParser::$embeddedDataName] += array('required' => true)
: $child[CommentParser::$embeddedDataName]['required'] = true;
if ($qualified = Scope::resolve($child['type'], $scope)) {
list($child['type'], $child['children'])
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
} elseif (
($contentType = Util::nestedValue($child, CommentParser::$embeddedDataName, 'type')) &&
($qualified = Scope::resolve($contentType, $scope))
) {
list($child['contentType'], $child['children'])
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
}
$children[$name] = $child;
}
} catch (Exception $e) {
if (String::endsWith($e->getFile(), 'CommentParser.php')) {
throw new RestException(500, "Error while parsing comments of `$className` class. " . $e->getMessage());
}
throw $e;
}
static::$models[$className] = array($className, $children);
return static::$models[$className];
}
/**
* Import previously created routes from cache
*
* @param array $routes
*/
public static function fromArray(array $routes)
{
static::$routes = $routes;
}
/**
* Export current routes for cache
*
* @return array
*/
public static function toArray()
{
return static::$routes;
}
public static function type($var)
{
if (is_object($var)) return get_class($var);
if (is_array($var)) return 'array';
if (is_bool($var)) return 'boolean';
if (is_numeric($var)) return is_float($var) ? 'float' : 'int';
return 'string';
}
/**
* Create a label from name of the parameter or property
*
* Convert `camelCase` style names into proper `Title Case` names
*
* @param string $name
*
* @return string
*/
public static function label($name)
{
return ucfirst(preg_replace(array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'), ' $0', $name));
}
public static function scope(ReflectionClass $class)
{
$namespace = $class->getNamespaceName();
$imports = array(
'*' => empty($namespace) ? '' : $namespace . '\\'
);
$file = file_get_contents($class->getFileName());
$tokens = token_get_all($file);
$namespace = '';
$alias = '';
$reading = false;
$last = 0;
foreach ($tokens as $token) {
if (is_string($token)) {
if ($reading && ',' == $token) {
//===== STOP =====//
$reading = false;
if (!empty($namespace))
$imports[$alias] = trim($namespace, '\\');
//===== START =====//
$reading = true;
$namespace = '';
$alias = '';
} else {
//===== STOP =====//
$reading = false;
if (!empty($namespace))
$imports[$alias] = trim($namespace, '\\');
}
} elseif (T_USE == $token[0]) {
//===== START =====//
$reading = true;
$namespace = '';
$alias = '';
} elseif ($reading) {
//echo token_name($token[0]) . ' ' . $token[1] . PHP_EOL;
switch ($token[0]) {
case T_WHITESPACE:
continue 2;
case T_STRING:
$alias = $token[1];
if (T_AS == $last) {
break;
}
//don't break;
case T_NS_SEPARATOR:
$namespace .= $token[1];
break;
}
$last = $token[0];
}
}
return $imports;
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace Luracast\Restler;
/**
* Scope resolution class, manages instantiation and acts as a dependency
* injection container
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Scope
{
public static $classAliases = array(
//Core
'Restler' => 'Luracast\Restler\Restler',
//Format classes
'AmfFormat' => 'Luracast\Restler\Format\AmfFormat',
'JsFormat' => 'Luracast\Restler\Format\JsFormat',
'JsonFormat' => 'Luracast\Restler\Format\JsonFormat',
'HtmlFormat' => 'Luracast\Restler\Format\HtmlFormat',
'PlistFormat' => 'Luracast\Restler\Format\PlistFormat',
'UploadFormat' => 'Luracast\Restler\Format\UploadFormat',
'UrlEncodedFormat' => 'Luracast\Restler\Format\UrlEncodedFormat',
'XmlFormat' => 'Luracast\Restler\Format\XmlFormat',
'YamlFormat' => 'Luracast\Restler\Format\YamlFormat',
'CsvFormat' => 'Luracast\Restler\Format\CsvFormat',
'TsvFormat' => 'Luracast\Restler\Format\TsvFormat',
//Filter classes
'RateLimit' => 'Luracast\Restler\Filter\RateLimit',
//UI classes
'Forms' => 'Luracast\Restler\UI\Forms',
'Nav' => 'Luracast\Restler\UI\Nav',
'Emmet' => 'Luracast\Restler\UI\Emmet',
'T' => 'Luracast\Restler\UI\Tags',
//API classes
'Resources' => 'Luracast\Restler\Resources',
//Cache classes
'HumanReadableCache' => 'Luracast\Restler\HumanReadableCache',
'ApcCache' => 'Luracast\Restler\ApcCache',
//Utility classes
'Object' => 'Luracast\Restler\Data\Object',
'String' => 'Luracast\Restler\Data\String',
'Arr' => 'Luracast\Restler\Data\Arr',
//Exception
'RestException' => 'Luracast\Restler\RestException'
);
public static $properties = array();
protected static $instances = array();
protected static $registry = array();
public static function register($name, Callable $function, $singleton = true)
{
static::$registry[$name] = (object)compact('function', 'singleton');
}
public static function set($name, $instance)
{
static::$instances[$name] = (object)array('instance' => $instance);
}
public static function get($name)
{
$r = null;
$initialized = false;
$properties = array();
if (array_key_exists($name, static::$instances)) {
$initialized = true;
$r = static::$instances[$name]->instance;
} elseif (!empty(static::$registry[$name])) {
$function = static::$registry[$name]->function;
$r = $function();
if (static::$registry[$name]->singleton)
static::$instances[$name] = (object)array('instance' => $r);
} else {
$fullName = $name;
if (isset(static::$classAliases[$name])) {
$fullName = static::$classAliases[$name];
}
if (class_exists($fullName)) {
$shortName = Util::getShortName($name);
$r = new $fullName();
static::$instances[$name] = (object)array('instance' => $r);
if ($name != 'Restler') {
$r->restler = static::get('Restler');
$m = Util::nestedValue($r->restler, 'apiMethodInfo', 'metadata');
if ($m) {
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
} else {
static::$instances[$name]->initPending = true;
}
}
}
}
if (
$r instanceof iUseAuthentication &&
static::get('Restler')->_authVerified &&
!isset(static::$instances[$name]->authVerified)
) {
static::$instances[$name]->authVerified = true;
$r->__setAuthenticationStatus
(static::get('Restler')->_authenticated);
}
if (isset(static::$instances[$name]->initPending)) {
$m = Util::nestedValue(static::get('Restler'), 'apiMethodInfo', 'metadata');
$fullName = $name;
if (class_exists($name)) {
$shortName = Util::getShortName($name);
} else {
$shortName = $name;
if (isset(static::$classAliases[$name]))
$fullName = static::$classAliases[$name];
}
if ($m) {
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
unset(static::$instances[$name]->initPending);
$initialized = false;
}
}
if (!$initialized && is_object($r)) {
$properties += static::$properties;
$objectVars = get_object_vars($r);
$className = get_class($r);
foreach ($properties as $property => $value) {
if (property_exists($className, $property)) {
//if not a static property
array_key_exists($property, $objectVars)
? $r->{$property} = $value
: $r::$$property = $value;
}
}
}
return $r;
}
/**
* Get fully qualified class name for the given scope
*
* @param string $className
* @param array $scope local scope
*
* @return string|boolean returns the class name or false
*/
public static function resolve($className, array $scope)
{
if (empty($className) || !is_string($className))
return false;
$divider = '\\';
$qualified = false;
if ($className{0} == $divider) {
$qualified = trim($className, $divider);
} elseif (array_key_exists($className, $scope)) {
$qualified = $scope[$className];
} else {
$qualified = $scope['*'] . $className;
}
if (class_exists($qualified))
return $qualified;
if (isset(static::$classAliases[$className])) {
$qualified = static::$classAliases[$className];
if (class_exists($qualified))
return $qualified;
}
return false;
}
}

View File

@ -0,0 +1,383 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\UI\Tags as T;
use Luracast\Restler\Util;
class Emmet
{
const DELIMITERS = '.#*>+^[=" ]{$@-#}';
/**
* Create the needed tag hierarchy from emmet string
*
* @param string $string
*
* @param array|string $data
*
* @return array|T
*/
public static function make($string, $data = null)
{
if (!strlen($string))
return array();
$implicitTag =
function () use (& $tag) {
if (empty($tag->tag)) {
switch ($tag->parent->tag) {
case 'ul':
case 'ol':
$tag->tag = 'li';
break;
case 'em':
$tag->tag = 'span';
break;
case 'table':
case 'tbody':
case 'thead':
case 'tfoot':
$tag->tag = 'tr';
break;
case 'tr':
$tag->tag = 'td';
break;
case 'select':
case 'optgroup':
$tag->tag = 'option';
break;
default:
$tag->tag = 'div';
}
}
};
$parseText =
function (
$text, $round, $total, $data, $delimiter = null
)
use (
& $tokens, & $tag
) {
$digits = 0;
if ($delimiter == null)
$delimiter = array(
'.' => true,
'#' => true,
'*' => true,
'>' => true,
'+' => true,
'^' => true,
'[' => true,
']' => true,
'=' => true,
);
while (!empty($tokens) &&
!isset($delimiter[$t = array_shift($tokens)])) {
while ('$' === $t) {
$digits++;
$t = array_shift($tokens);
}
if ($digits) {
$negative = false;
$offset = 0;
if ('@' == $t) {
if ('-' == ($t = array_shift($tokens))) {
$negative = true;
if (is_numeric(reset($tokens))) {
$offset = array_shift($tokens);
}
} elseif (is_numeric($t)) {
$offset = $t;
} else {
array_unshift($tokens, $t);
}
} elseif ('#' == ($h = array_shift($tokens))) {
if (!empty($t)) {
$data = Util::nestedValue($data, $t);
if (is_null($data)) {
return null;
}
}
if (is_numeric($data)) {
$text .= sprintf("%0{$digits}d", (int)$data);
} elseif (is_string($data)) {
$text .= $data;
}
$digits = 0;
continue;
} else {
array_unshift($tokens, $t, $h);
}
if ($negative) {
$n = $total + 1 - $round + $offset;
} else {
$n = $round + $offset;
}
$text .= sprintf("%0{$digits}d", $n);
$digits = 0;
} else {
$text .= $t;
}
}
if (isset($t))
array_unshift($tokens, $t);
return $text;
};
$parseAttributes =
function (Callable $self, $round, $total, $data)
use (& $tokens, & $tag, $parseText) {
$a = $parseText(
'', $round, $total, $data
);
if (is_null($a))
return;
if ('=' == ($v = array_shift($tokens))) {
//value
if ('"' == ($v = array_shift($tokens))) {
$text = '';
$tag->$a($parseText(
$text, $round, $total, $data,
array('"' => true)
));
} else {
array_unshift($tokens, $v);
$text = '';
$tag->$a($parseText(
$text, $round, $total, $data,
array(' ' => true, ']' => true)
));
}
if (' ' == ($v = array_shift($tokens))) {
$self($self, $round, $total, $data);
}
} elseif (']' == $v) {
//end
$tag->$a('');
return;
} elseif (' ' == $v) {
$tag->$a('');
$self($self, $round, $total, $data);
}
};
$tokens = static::tokenize($string);
$tag = new T(array_shift($tokens));
$parent = $root = new T;
$parse =
function (
Callable $self, $round = 1, $total = 1
)
use (
& $tokens, & $parent, & $tag, & $data,
$parseAttributes, $implicitTag, $parseText
) {
$offsetTokens = null;
$parent[] = $tag;
$isInChild = false;
while ($tokens) {
switch (array_shift($tokens)) {
//class
case '.':
$offsetTokens = array_values($tokens);
array_unshift($offsetTokens, '.');
$implicitTag();
$e = array_filter(explode(' ', $tag->class));
$e[] = $parseText('', $round, $total, $data);
$tag->class(implode(' ', array_unique($e)));
break;
//id
case '#':
$offsetTokens = array_values($tokens);
array_unshift($offsetTokens, '#');
$implicitTag();
$tag->id(
$parseText(
array_shift($tokens), $round, $total, $data
)
);
break;
//attributes
case '[':
$offsetTokens = array_values($tokens);
array_unshift($offsetTokens, '[');
$implicitTag();
$parseAttributes(
$parseAttributes, $round, $total, $data
);
break;
//child
case '{':
$text = '';
$tag[] = $parseText(
$text, $round, $total, $data, array('}' => true)
);
break;
case '>':
$isInChild = true;
$offsetTokens = null;
if ('{' == ($t = array_shift($tokens))) {
array_unshift($tokens, $t);
$child = new T();
$tag[] = $child;
$parent = $tag;
$tag = $child;
} elseif ('[' == $t) {
array_unshift($tokens, $t);
} else {
$child = new T($t);
$tag[] = $child;
$parent = $tag;
$tag = $child;
}
break;
//sibling
case '+':
$offsetTokens = null;
if (!$isInChild && $round != $total) {
$tokens = array();
break;
}
if ('{' == ($t = array_shift($tokens))) {
$tag = $tag->parent;
array_unshift($tokens, $t);
break;
} elseif ('[' == $t) {
array_unshift($tokens, $t);
} else {
$child = new T($t);
$tag = $tag->parent;
$tag[] = $child;
$tag = $child;
}
break;
//sibling of parent
case '^':
if ($round != $total) {
$tokens = array();
break;
}
$tag = $tag->parent;
if ($tag->parent)
$tag = $tag->parent;
while ('^' == ($t = array_shift($tokens))) {
if ($tag->parent)
$tag = $tag->parent;
}
$child = new T($t);
$tag[] = $child;
$tag = $child;
break;
//clone
case '*':
$times = array_shift($tokens);
$removeCount = 2;
$delimiter = array(
'.' => true,
'#' => true,
'*' => true,
'>' => true,
'+' => true,
'^' => true,
'[' => true,
']' => true,
'=' => true,
);
if (!is_numeric($times)) {
if (is_string($times)) {
if (!isset($delimiter[$times])) {
$data = Util::nestedValue($data, $times)
? : $data;
} else {
array_unshift($tokens, $times);
$removeCount = 1;
}
}
$indexed = array_values($data);
$times = is_array($data) && $indexed == $data
? count($data) : 0;
}
$source = $tag;
if (!empty($offsetTokens)) {
if (false !== strpos($source->class, ' ')) {
$class = explode(' ', $source->class);
array_pop($class);
$class = implode(' ', $class);
} else {
$class = null;
}
$tag->class($class);
$star = array_search('*', $offsetTokens);
array_splice($offsetTokens, $star, $removeCount);
$remainingTokens = $offsetTokens;
} else {
$remainingTokens = $tokens;
}
$source->parent = null;
$sourceData = $data;
$currentParent = $parent;
for ($i = 1; $i <= $times; $i++) {
$tag = clone $source;
$parent = $currentParent;
$data = is_array($sourceData)
&& isset($sourceData[$i - 1])
? $sourceData[$i - 1]
: @(string)$sourceData;
$tokens = array_values($remainingTokens);
$self($self, $i, $times);
}
$round = 1;
$offsetTokens = null;
$tag = $source;
$tokens = array(); //$remainingTokens;
break;
}
}
};
$parse($parse);
return count($root) == 1 ? $root[0] : $root;
}
public static function tokenize($string)
{
$r = array();
$f = strtok($string, static::DELIMITERS);
$pos = 0;
do {
$start = $pos;
$pos = strpos($string, $f, $start);
$tokens = array();
for ($i = $start; $i < $pos; $i++) {
$token = $string{$i};
if (('#' == $token || '.' == $token) &&
(!empty($tokens) || $i == 0)
) {
$r[] = '';
}
$r[] = $tokens[] = $token;
}
$pos += strlen($f);
$r[] = $f;
} while (false != ($f = strtok(static::DELIMITERS)));
for ($i = $pos; $i < strlen($string); $i++) {
$token = $string{$i};
$r[] = $tokens[] = $token;
}
return $r;
/* sample output produced by ".row*3>.col*3"
[0] => div
[1] => .
[2] => row
[3] => *
[4] => 3
[5] => >
[6] => div
[7] => .
[8] => col
[9] => *
[10] => 4
*/
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Luracast\Restler\UI;
/**
* Utility class for providing preset styles for html forms
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class FormStyles
{
public static $html = array(
'form' => 'form[role=form id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => '.row>section>label{$label#}^input[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => '.row>label{$label#}^textarea[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => '.row>section>label{$label#}^span>label*options>input[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{ $text#}',
'select' => '.row>label{$label#}^select[name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => '.row>label{ &nbsp; }^button[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.row>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}+section*options>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $text#}',
'select-array' => 'label{$label#}+select[name=$name# required=$required# multiple style="height: auto;background-image: none; outline: inherit;"]>option[value=$value# selected=$selected#]{$text#}*options',
);
public static $bootstrap3 = array(
'form' => 'form[role=form id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => '.form-group>label{$label#}+input.form-control[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => '.form-group>label{$label#}+textarea.form-control[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => 'fieldset>legend{$label#}>.radio*options>label>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]{$text#}',
'select' => '.form-group>label{$label#}+select.form-control[name=$name# multiple=$multiple# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => 'button.btn.btn-primary[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.checkbox>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}>.checkbox*options>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required#]{$text#}',
'select-array' => '.form-group>label{$label#}+select.form-control[name=$name# multiple=$multiple# required=$required#] size=$options#>option[value=$value# selected=$selected#]{$text#}*options',
//------------- CUSTOM STYLES ---------------------//
'radio-inline' => '.form-group>label{$label# : &nbsp;}+label.radio-inline*options>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{$text#}',
);
public static $foundation5 = array(
'form' => 'form[id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => 'label{$label#}+input[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => 'label{$label#}+textarea[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => 'label{$label# : &nbsp;}+label.radio-inline*options>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{$text#}',
'select' => 'label{$label#}+select[name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => 'button.button[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => 'label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}+label*options>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $text#}',
'select-array' => 'label{$label#}+select[name=$name# required=$required# multiple style="height: auto;background-image: none; outline: inherit;"]>option[value=$value# selected=$selected#]{$text#}*options',
//------------- CUSTOM STYLES ---------------------//
);
}

View File

@ -0,0 +1,434 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Defaults;
use Luracast\Restler\Format\UploadFormat;
use Luracast\Restler\Format\UrlEncodedFormat;
use Luracast\Restler\iFilter;
use Luracast\Restler\RestException;
use Luracast\Restler\Restler;
use Luracast\Restler\Routes;
use Luracast\Restler\Scope;
use Luracast\Restler\UI\Tags as T;
use Luracast\Restler\User;
use Luracast\Restler\Util;
/**
* Utility class for automatically generating forms for the given http method
* and api url
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Forms implements iFilter
{
const FORM_KEY = 'form_key';
public static $filterFormRequestsOnly = false;
public static $excludedPaths = array();
public static $style;
/**
* @var bool should we fill up the form using given data?
*/
public static $preFill = true;
/**
* @var ValidationInfo
*/
public static $validationInfo = null;
protected static $inputTypes = array(
'hidden',
'password',
'button',
'image',
'file',
'reset',
'submit',
'search',
'checkbox',
'radio',
'email',
'text',
'color',
'date',
'datetime',
'datetime-local',
'email',
'month',
'number',
'range',
'search',
'tel',
'time',
'url',
'week',
);
protected static $fileUpload = false;
private static $key = array();
/**
* @var ApiMethodInfo;
*/
private static $info;
/**
* Get the form
*
* @param string $method http method to submit the form
* @param string $action relative path from the web root. When set to null
* it uses the current api method's path
* @param bool $dataOnly if you want to render the form yourself use this
* option
* @param string $prefix used for adjusting the spacing in front of
* form elements
* @param string $indent used for adjusting indentation
*
* @return array|T
*
* @throws \Luracast\Restler\RestException
*/
public static function get($method = 'POST', $action = null, $dataOnly = false, $prefix = '', $indent = ' ')
{
if (!static::$style)
static::$style = FormStyles::$html;
try {
/** @var Restler $restler */
$restler = Scope::get('Restler');
if (is_null($action))
$action = $restler->url;
$info = $restler->url == $action
&& Util::getRequestMethod() == $method
? $restler->apiMethodInfo
: Routes::find(
trim($action, '/'),
$method,
$restler->getRequestedApiVersion(),
static::$preFill ||
($restler->requestMethod == $method &&
$restler->url == $action)
? $restler->getRequestData()
: array()
);
} catch (RestException $e) {
//echo $e->getErrorMessage();
$info = false;
}
if (!$info)
throw new RestException(500, 'invalid action path for form `' . $method . ' ' . $action . '`');
static::$info = $info;
$m = $info->metadata;
$r = static::fields($dataOnly);
if ($method != 'GET' && $method != 'POST') {
if (empty(Defaults::$httpMethodOverrideProperty))
throw new RestException(
500,
'Forms require `Defaults::\$httpMethodOverrideProperty`' .
"for supporting HTTP $method"
);
if ($dataOnly) {
$r[] = array(
'tag' => 'input',
'name' => Defaults::$httpMethodOverrideProperty,
'type' => 'hidden',
'value' => 'method',
);
} else {
$r[] = T::input()
->name(Defaults::$httpMethodOverrideProperty)
->value($method)
->type('hidden');
}
$method = 'POST';
}
if (session_id() != '') {
$form_key = static::key($method, $action);
if ($dataOnly) {
$r[] = array(
'tag' => 'input',
'name' => static::FORM_KEY,
'type' => 'hidden',
'value' => 'hidden',
);
} else {
$key = T::input()
->name(static::FORM_KEY)
->type('hidden')
->value($form_key);
$r[] = $key;
}
}
$s = array(
'tag' => 'button',
'type' => 'submit',
'label' =>
Util::nestedValue($m, 'return', CommentParser::$embeddedDataName, 'label')
? : 'Submit'
);
if (!$dataOnly)
$s = Emmet::make(static::style('submit', $m), $s);
$r[] = $s;
$t = array(
'action' => $restler->getBaseUrl() . '/' . rtrim($action, '/'),
'method' => $method,
);
if (static::$fileUpload) {
static::$fileUpload = false;
$t['enctype'] = 'multipart/form-data';
}
if (!$dataOnly) {
$t = Emmet::make(static::style('form', $m), $t);
$t->prefix = $prefix;
$t->indent = $indent;
$t[] = $r;
} else {
$t['fields'] = $r;
}
return $t;
}
public static function style($name, array $metadata, $type = '')
{
return isset($metadata[CommentParser::$embeddedDataName][$name])
? $metadata[CommentParser::$embeddedDataName][$name]
: (!empty($type) && isset(static::$style["$name-$type"])
? static::$style["$name-$type"]
: (isset(static::$style[$name])
? static::$style[$name]
: null
)
);
}
public static function fields($dataOnly = false)
{
$m = static::$info->metadata;
$params = $m['param'];
$values = static::$info->parameters;
$r = array();
foreach ($params as $k => $p) {
$value = Util::nestedValue($values, $k);
if (
is_scalar($value) ||
($p['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
is_object($value) && $p['type'] == get_class($value)
)
$p['value'] = $value;
static::$validationInfo = $v = new ValidationInfo($p);
if ($v->from == 'path')
continue;
if (!empty($v->children)) {
$t = Emmet::make(static::style('fieldset', $m), array('label' => $v->label));
foreach ($v->children as $n => $c) {
$value = Util::nestedValue($v->value, $n);
if (
is_scalar($value) ||
($c['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
is_object($value) && $c['type'] == get_class($value)
)
$c['value'] = $value;
static::$validationInfo = $vc = new ValidationInfo($c);
if ($vc->from == 'path')
continue;
$vc->name = $v->name . '[' . $vc->name . ']';
$t [] = static::field($vc, $dataOnly);
}
$r[] = $t;
static::$validationInfo = null;
} else {
$f = static::field($v, $dataOnly);
$r [] = $f;
}
static::$validationInfo = null;
}
return $r;
}
/**
* @param ValidationInfo $p
*
* @param bool $dataOnly
*
* @return array|T
*/
public static function field(ValidationInfo $p, $dataOnly = false)
{
if (is_string($p->value)) {
//prevent XSS attacks
$p->value = htmlspecialchars($p->value, ENT_QUOTES | ENT_HTML401, 'UTF-8');
}
$type = $p->field ? : static::guessFieldType($p);
$tag = in_array($type, static::$inputTypes)
? 'input' : $type;
$options = array();
$name = $p->name;
$multiple = null;
if ($p->type == 'array' && $p->contentType != 'associative') {
$name .= '[]';
$multiple = true;
}
if ($p->choice) {
foreach ($p->choice as $i => $choice) {
$option = array('name' => $name, 'value' => $choice);
$option['text'] = isset($p->rules['select'][$i])
? $p->rules['select'][$i]
: $choice;
if ($choice == $p->value)
$option['selected'] = true;
$options[] = $option;
}
} elseif ($p->type == 'boolean' || $p->type == 'bool') {
if (String::beginsWith($type, 'radio')) {
$options[] = array('name' => $p->name, 'text' => ' Yes ',
'value' => 'true');
$options[] = array('name' => $p->name, 'text' => ' No ',
'value' => 'false');
if ($p->value || $p->default)
$options[0]['selected'] = true;
} else {
$r = array(
'tag' => $tag,
'name' => $name,
'type' => $type,
'label' => $p->label,
'value' => 'true',
'default' => $p->default,
);
$r['text'] = 'Yes';
if ($p->default) {
$r['selected'] = true;
}
}
}
if (empty($r)) {
$r = array(
'tag' => $tag,
'name' => $name,
'type' => $type,
'label' => $p->label,
'value' => $p->value,
'default' => $p->default,
'options' => & $options,
'multiple' => $multiple,
);
}
if ($type == 'file') {
static::$fileUpload = true;
$r['accept'] = implode(', ', UploadFormat::$allowedMimeTypes);
}
if (true === $p->required)
$r['required'] = true;
if (isset($p->rules['autofocus']))
$r['autofocus'] = true;
/*
echo "<pre>";
print_r($r);
echo "</pre>";
*/
if ($dataOnly)
return $r;
if (isset($p->rules['form']))
return Emmet::make($p->rules['form'], $r);
$m = static::$info->metadata;
$t = Emmet::make(static::style($type, $m, $p->type) ? : static::style($tag, $m, $p->type), $r);
return $t;
}
protected static function guessFieldType(ValidationInfo $p, $type = 'type')
{
if (in_array($p->$type, static::$inputTypes))
return $p->$type;
if ($p->choice)
return $p->type == 'array' ? 'checkbox' : 'select';
switch ($p->$type) {
case 'boolean':
return 'radio';
case 'int':
case 'number':
case 'float':
return 'number';
case 'array':
return static::guessFieldType($p, 'contentType');
}
if ($p->name == 'password')
return 'password';
return 'text';
}
/**
* Get the form key
*
* @param string $method http method for form key
* @param string $action relative path from the web root. When set to null
* it uses the current api method's path
*
* @return string generated form key
*/
public static function key($method = 'POST', $action = null)
{
if (is_null($action))
$action = Scope::get('Restler')->url;
$target = "$method $action";
if (empty(static::$key[$target]))
static::$key[$target] = md5($target . User::getIpAddress() . uniqid(mt_rand()));
$_SESSION[static::FORM_KEY] = static::$key;
return static::$key[$target];
}
/**
* Access verification method.
*
* API access will be denied when this method returns false
*
* @return boolean true when api access is allowed false otherwise
*
* @throws RestException 403 security violation
*/
public function __isAllowed()
{
if (session_id() == '') {
session_start();
}
/** @var Restler $restler */
$restler = $this->restler;
$url = $restler->url;
foreach (static::$excludedPaths as $exclude) {
if (empty($exclude)) {
if ($url == $exclude)
return true;
} elseif (String::beginsWith($url, $exclude)) {
return true;
}
}
$check = static::$filterFormRequestsOnly
? $restler->requestFormat instanceof UrlEncodedFormat || $restler->requestFormat instanceof UploadFormat
: true;
if (!empty($_POST) && $check) {
if (
isset($_POST[static::FORM_KEY]) &&
($target = Util::getRequestMethod() . ' ' . $restler->url) &&
isset($_SESSION[static::FORM_KEY][$target]) &&
$_POST[static::FORM_KEY] == $_SESSION[static::FORM_KEY][$target]
) {
return true;
}
throw new RestException(403, 'Insecure form submission');
}
return true;
}
}

View File

@ -0,0 +1,208 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Defaults;
use Luracast\Restler\Restler;
use Luracast\Restler\Routes;
use Luracast\Restler\Scope;
use Luracast\Restler\Util;
/**
* Utility class for automatically creating data to build an navigation interface
* based on available routes that are accessible by the current user
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Nav
{
public static $root = 'home';
/**
* @var null|callable if the api methods are under access control mechanism
* you can attach a function here that returns true or false to determine
* visibility of a protected api method. this function will receive method
* info as the only parameter.
*/
public static $accessControlFunction = null;
/**
* @var array all paths beginning with any of the following will be excluded
* from documentation. if an empty string is given it will exclude the root
*/
public static $excludedPaths = array('');
/**
* @var array prefix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
*/
public static $prepends = array();
/**
* @var array suffix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
*/
public static $appends = array();
public static $addExtension = true;
protected static $extension = '';
public static function get($for = '', $activeUrl = null)
{
if (!static::$accessControlFunction && Defaults::$accessControlFunction)
static::$accessControlFunction = Defaults::$accessControlFunction;
/** @var Restler $restler */
$restler = Scope::get('Restler');
if (static::$addExtension)
static::$extension = '.' . $restler->responseFormat->getExtension();
if (is_null($activeUrl))
$activeUrl = $restler->url;
$tree = array();
foreach (static::$prepends as $path => $text) {
$url = null;
if (is_array($text)) {
if (isset($text['url'])) {
$url = $text['url'];
$text = $text['text'];
} else {
$url = current(array_keys($text));
$text = current($text);
}
}
if (is_numeric($path)) {
$path = $text;
$text = null;
}
if (empty($for) || 0 === strpos($path, "$for/"))
static::build($tree, $path, $url, $text, $activeUrl);
}
$routes = Routes::toArray();
$routes = $routes['v' . $restler->getRequestedApiVersion()];
foreach ($routes as $value) {
foreach ($value as $httpMethod => $route) {
if ($httpMethod != 'GET') {
continue;
}
$path = $route['url'];
if (false !== strpos($path, '{'))
continue;
if ($route['accessLevel'] > 1 && !Util::$restler->_authenticated)
continue;
foreach (static::$excludedPaths as $exclude) {
if (empty($exclude)) {
if (empty($path))
continue 2;
} elseif (0 === strpos($path, $exclude)) {
continue 2;
}
}
if ($restler->_authenticated
&& static::$accessControlFunction
&& (!call_user_func(
static::$accessControlFunction, $route['metadata']))
) {
continue;
}
$text = Util::nestedValue(
$route,
'metadata',
CommentParser::$embeddedDataName,
'label'
);
if (empty($for) || 0 === strpos($path, "$for/"))
static::build($tree, $path, null, $text, $activeUrl);
}
}
foreach (static::$appends as $path => $text) {
$url = null;
if (is_array($text)) {
if (isset($text['url'])) {
$url = $text['url'];
$text = $text['text'];
} else {
$url = current(array_keys($text));
$text = current($text);
}
}
if (is_numeric($path)) {
$path = $text;
$text = null;
}
if (empty($for) || 0 === strpos($path, "$for/"))
static::build($tree, $path, $url, $text, $activeUrl);
}
if (!empty($for)) {
$for = explode('/', $for);
$p = & $tree;
foreach ($for as $f) {
if (isset($p[$f]['children'])) {
$p = & $p[$f]['children'];
} else {
return array();
}
}
return $p;
}
return $tree;
}
protected static function build(&$tree, $path,
$url = null, $text = null, $activeUrl = null)
{
$parts = explode('/', $path);
if (count($parts) == 1 && empty($parts[0]))
$parts = array(static::$root);
$p = & $tree;
$end = end($parts);
foreach ($parts as $part) {
if (!isset($p[$part])) {
$p[$part] = array(
'href' => '#',
'text' => static::title($part)
);
if ($part == $end) {
$p[$part]['class'] = $part;
if ($text)
$p[$part]['text'] = $text;
if (is_null($url)) {
if (empty($path) && !empty(static::$extension))
$path = 'index';
$p[$part]['href'] = Util::$restler->getBaseUrl()
. '/' . $path . static::$extension;
} else {
if (empty($url) && !empty(static::$extension))
$url = 'index';
$p[$part]['href'] = $url . static::$extension;
}
if ($path == $activeUrl) {
$p[$part]['active'] = true;
}
}
$p[$part]['children'] = array();
}
$p = & $p[$part]['children'];
}
}
protected static function title($name)
{
if (empty($name)) {
$name = static::$root;
} else {
$name = ltrim($name, '#');
}
return ucfirst(preg_replace(array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'), ' $0', $name));
}
}

View File

@ -0,0 +1,282 @@
<?php
namespace Luracast\Restler\UI;
use ArrayAccess;
use Countable;
use Luracast\Restler\Util;
/**
* Utility class for generating html tags in an object oriented way
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*
* ============================ magic properties ==============================
* @property Tags parent parent tag
* ============================== magic methods ===============================
* @method Tags name(string $value) name attribute
* @method Tags action(string $value) action attribute
* @method Tags placeholder(string $value) placeholder attribute
* @method Tags value(string $value) value attribute
* @method Tags required(boolean $value) required attribute
* @method Tags class(string $value) required attribute
*
* =========================== static magic methods ============================
* @method static Tags form() creates a html form
* @method static Tags input() creates a html input element
* @method static Tags button() creates a html button element
*
*/
class Tags implements ArrayAccess, Countable
{
public static $humanReadable = true;
public static $initializer = null;
protected static $instances = array();
public $prefix = '';
public $indent = ' ';
public $tag;
protected $attributes = array();
protected $children = array();
protected $_parent;
public function __construct($name = null, array $children = array())
{
$this->tag = $name;
$c = array();
foreach ($children as $child) {
is_array($child)
? $c = array_merge($c, $child)
: $c [] = $child;
}
$this->markAsChildren($c);
$this->children = $c;
if (static::$initializer)
call_user_func_array(static::$initializer, array(& $this));
}
/**
* Get Tag by id
*
* Retrieve a tag by its id attribute
*
* @param string $id
*
* @return Tags|null
*/
public static function byId($id)
{
return Util::nestedValue(static::$instances, $id);
}
/**
* @param $name
* @param array $children
*
* @return Tags
*/
public static function __callStatic($name, array $children)
{
return new static($name, $children);
}
public function toString($prefix = '', $indent = ' ')
{
$this->prefix = $prefix;
$this->indent = $indent;
return $this->__toString();
}
public function __toString()
{
$children = '';
if (static::$humanReadable) {
$lineBreak = false;
foreach ($this->children as $key => $child) {
$prefix = $this->prefix;
if (!is_null($this->tag))
$prefix .= $this->indent;
if ($child instanceof $this) {
$child->prefix = $prefix;
$child->indent = $this->indent;
$children .= PHP_EOL . $child;
$lineBreak = true;
} else {
$children .= $child;
}
}
if ($lineBreak)
$children .= PHP_EOL . $this->prefix;
} else {
$children = implode('', $this->children);
}
if (is_null($this->tag))
return $children;
$attributes = '';
foreach ($this->attributes as $attribute => &$value)
$attributes .= " $attribute=\"$value\"";
if (count($this->children))
return static::$humanReadable
? "$this->prefix<{$this->tag}{$attributes}>"
. "$children"
. "</{$this->tag}>"
: "<{$this->tag}{$attributes}>$children</{$this->tag}>";
return "$this->prefix<{$this->tag}{$attributes}/>";
}
public function toArray()
{
$r = array();
$r['attributes'] = $this->attributes;
$r['tag'] = $this->tag;
$children = array();
foreach ($this->children as $key => $child) {
$children[$key] = $child instanceof $this
? $child->toArray()
: $child;
}
$r['children'] = $children;
return $r;
}
/**
* Set the id attribute of the current tag
*
* @param string $value
*
* @return string
*/
public function id($value)
{
$this->attributes['id'] = isset($value)
? (string)$value
: Util::nestedValue($this->attributes, 'name');
static::$instances[$value] = $this;
return $this;
}
public function __get($name)
{
if ('parent' == $name)
return $this->_parent;
if (isset($this->attributes[$name]))
return $this->attributes[$name];
return;
}
public function __set($name, $value)
{
if ('parent' == $name) {
if ($this->_parent) {
unset($this->_parent[array_search($this, $this->_parent->children)]);
}
if (!empty($value)) {
$value[] = $this;
}
}
}
public function __isset($name)
{
return isset($this->attributes[$name]);
}
/**
* @param $attribute
* @param $value
*
* @return Tags
*/
public function __call($attribute, $value)
{
if (is_null($value)) {
return isset($this->attributes[$attribute])
? $this->attributes[$attribute]
: null;
}
$value = $value[0];
if (is_null($value)) {
unset($this->attributes[$attribute]);
return $this;
}
$this->attributes[$attribute] = is_bool($value)
? ($value ? 'true' : 'false')
: @(string)$value;
return $this;
}
public function offsetGet($index)
{
if ($this->offsetExists($index)) {
return $this->children[$index];
}
return false;
}
public function offsetExists($index)
{
return isset($this->children[$index]);
}
public function offsetSet($index, $value)
{
if ($index) {
$this->children[$index] = $value;
} elseif (is_array($value)) {
$c = array();
foreach ($value as $child) {
is_array($child)
? $c = array_merge($c, $child)
: $c [] = $child;
}
$this->markAsChildren($c);
$this->children += $c;
} else {
$c = array($value);
$this->markAsChildren($c);
$this->children[] = $value;
}
return true;
}
public function offsetUnset($index)
{
$this->children[$index]->_parent = null;
unset($this->children[$index]);
return true;
}
public function getContents()
{
return $this->children;
}
public function count()
{
return count($this->children);
}
private function markAsChildren(& $children)
{
foreach ($children as $i => $child) {
if (is_string($child))
continue;
if (!is_object($child)) {
unset($children[$i]);
continue;
}
//echo $child;
if (isset($child->_parent) && $child->_parent != $this) {
//remove from current parent
unset($child->_parent[array_search($child, $child->_parent->children)]);
}
$child->_parent = $this;
}
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Luracast\Restler;
/**
* Information gathered about the api user is kept here using static methods
* and properties for other classes to make use of them.
* Typically Authentication classes populate them
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class User implements iIdentifyUser
{
private static $initialized = false;
public static $id = null;
public static $cacheId = null;
public static $ip;
public static $browser = '';
public static $platform = '';
public static function init()
{
static::$initialized = true;
static::$ip = static::getIpAddress();
}
public static function getUniqueIdentifier($includePlatform = false)
{
if (!static::$initialized) static::init();
return static::$id ? : base64_encode('ip:' . ($includePlatform
? static::$ip . '-' . static::$platform
: static::$ip
));
}
public static function getIpAddress($ignoreProxies = false)
{
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED',
'REMOTE_ADDR') as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip); // just to be safe
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4
| FILTER_FLAG_NO_PRIV_RANGE
| FILTER_FLAG_NO_RES_RANGE) !== false
) {
return $ip;
}
}
}
}
}
/**
* Authentication classes should call this method
*
* @param string $id user id as identified by the authentication classes
*
* @return void
*/
public static function setUniqueIdentifier($id)
{
static::$id = $id;
}
/**
* User identity to be used for caching purpose
*
* When the dynamic cache service places an object in the cache, it needs to
* label it with a unique identifying string known as a cache ID. This
* method gives that identifier
*
* @return string
*/
public static function getCacheIdentifier()
{
return static::$cacheId ?: static::$id;
}
/**
* User identity for caching purpose
*
* In a role based access control system this will be based on role
*
* @param $id
*
* @return void
*/
public static function setCacheIdentifier($id)
{
static::$cacheId = $id;
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace Luracast\Restler;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Util
{
/**
* @var Restler instance injected at runtime
*/
public static $restler;
/**
* verify if the given data type string is scalar or not
*
* @static
*
* @param string $type data type as string
*
* @return bool true or false
*/
public static function isObjectOrArray($type)
{
if (is_array($type)) {
foreach ($type as $t) {
if (static::isObjectOrArray($t)) {
return true;
}
}
return false;
}
return !(boolean)strpos('|bool|boolean|int|float|string|', $type);
}
/**
* Get the value deeply nested inside an array / object
*
* Using isset() to test the presence of nested value can give a false positive
*
* This method serves that need
*
* When the deeply nested property is found its value is returned, otherwise
* false is returned.
*
* @param array $from array to extract the value from
* @param string|array $key ... pass more to go deeply inside the array
* alternatively you can pass a single array
*
* @return null|mixed null when not found, value otherwise
*/
public static function nestedValue($from, $key/**, $key2 ... $key`n` */)
{
if (is_array($key)) {
$keys = $key;
} else {
$keys = func_get_args();
array_shift($keys);
}
foreach ($keys as $key) {
if (is_array($from) && isset($from[$key])) {
$from = $from[$key];
continue;
} elseif (is_object($from) && isset($from->{$key})) {
$from = $from->{$key};
continue;
}
return null;
}
return $from;
}
public static function getResourcePath($className,
$resourcePath = null,
$prefix = '')
{
if (is_null($resourcePath)) {
if (Defaults::$autoRoutingEnabled) {
$resourcePath = strtolower($className);
if (false !== ($index = strrpos($className, '\\')))
$resourcePath = substr($resourcePath, $index + 1);
if (false !== ($index = strrpos($resourcePath, '_')))
$resourcePath = substr($resourcePath, $index + 1);
} else {
$resourcePath = '';
}
} else
$resourcePath = trim($resourcePath, '/');
if (strlen($resourcePath) > 0)
$resourcePath .= '/';
return $prefix . $resourcePath;
}
/**
* Compare two strings and remove the common
* sub string from the first string and return it
*
* @static
*
* @param string $fromPath
* @param string $usingPath
* @param string $char
* optional, set it as
* blank string for char by char comparison
*
* @return string
*/
public static function removeCommonPath($fromPath, $usingPath, $char = '/')
{
if (empty($fromPath))
return '';
$fromPath = explode($char, $fromPath);
$usingPath = explode($char, $usingPath);
while (count($usingPath)) {
if (count($fromPath) && $fromPath[0] == $usingPath[0]) {
array_shift($fromPath);
} else {
break;
}
array_shift($usingPath);
}
return implode($char, $fromPath);
}
/**
* Parses the request to figure out the http request type
*
* @static
*
* @return string which will be one of the following
* [GET, POST, PUT, PATCH, DELETE]
* @example GET
*/
public static function getRequestMethod()
{
$method = $_SERVER['REQUEST_METHOD'];
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
} elseif (
!empty(Defaults::$httpMethodOverrideProperty)
&& isset($_REQUEST[Defaults::$httpMethodOverrideProperty])
) {
// support for exceptional clients who can't set the header
$m = strtoupper($_REQUEST[Defaults::$httpMethodOverrideProperty]);
if ($m == 'PUT' || $m == 'DELETE' ||
$m == 'POST' || $m == 'PATCH'
) {
$method = $m;
}
}
// support for HEAD request
if ($method == 'HEAD') {
$method = 'GET';
}
return $method;
}
/**
* Pass any content negotiation header such as Accept,
* Accept-Language to break it up and sort the resulting array by
* the order of negotiation.
*
* @static
*
* @param string $accept header value
*
* @return array sorted by the priority
*/
public static function sortByPriority($accept)
{
$acceptList = array();
$accepts = explode(',', strtolower($accept));
if (!is_array($accepts)) {
$accepts = array($accepts);
}
foreach ($accepts as $pos => $accept) {
$parts = explode(';q=', trim($accept));
$type = array_shift($parts);
$quality = count($parts) ?
floatval(array_shift($parts)) :
(1000 - $pos) / 1000;
$acceptList[$type] = $quality;
}
arsort($acceptList);
return $acceptList;
}
public static function getShortName($className)
{
$className = explode('\\', $className);
return end($className);
}
}

View File

@ -0,0 +1,10 @@
<?php
/**
* Interface iAuthenticate only exists for compatibility mode for Restler 2 and below, it should
* not be used otherwise.
*/
interface iAuthenticate
{
public function __isAuthenticated();
}

View File

@ -0,0 +1,14 @@
<?php
/**
* Restler 1 compatibility mode enabler
*/
use Luracast\Restler\Defaults;
//changes in iAuthenticate
Defaults::$authenticationMethod = 'isAuthenticated';
include __DIR__ . '/iAuthenticate.php';
//changes in routing
Defaults::$autoRoutingEnabled = false;
Defaults::$smartParameterParsing = false;
Defaults::$autoValidationEnabled = false;

View File

@ -0,0 +1,40 @@
<?php
/**
* Restler 2 compatibility mode enabler
*/
use Luracast\Restler\Defaults;
use Luracast\Restler\AutoLoader;
use Luracast\Restler\CommentParser;
//changes in auto loading
$classMap = array();
//find lowercase php files representing a class/interface
foreach (explode(PATH_SEPARATOR, get_include_path()) as $path)
foreach (new DirectoryIterator($path) as $fileInfo)
if ($fileInfo->isFile()
&& 'php' === $fileInfo->getExtension()
&& ctype_lower($fileInfo->getBasename('.php'))
&& preg_match(
'/^ *(class|interface|abstract +class)'
. ' +([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/m',
file_get_contents($fileInfo->getPathname()),
$matches
)
)
$classMap[$matches[2]] = $fileInfo->getPathname();
AutoLoader::seen($classMap);
//changes in iAuthenticate
Defaults::$authenticationMethod = '__isAuthenticated';
include __DIR__ . '/iAuthenticate.php';
//changes in auto routing
Defaults::$smartAutoRouting = false;
Defaults::$smartParameterParsing = false;
Defaults::$autoValidationEnabled = false;
//changes in parsing embedded data in comments
CommentParser::$embeddedDataPattern = '/\((\S+)\)/ms';
CommentParser::$embeddedDataIndex = 1;

View File

@ -0,0 +1,70 @@
{
"name":"restler/framework",
"description":"Just the Restler Framework without the tests and examples",
"type":"library",
"keywords":["server","api","framework","REST"],
"homepage":"http://luracast.com/products/restler/",
"license":"LGPL-2.1",
"authors":[
{
"name":"Luracast",
"email":"arul@luracast.com"
},
{
"name":"Nick nickl- Lombard",
"email":"github@jigsoft.co.za"
}
],
"extra":{
"branch-alias":{
"master":"v3.0.x-dev"
}
},
"suggest":{
"luracast/explorer":"Restler's very own api explorer (see require-dev for details)",
"rodneyrehm/plist":"Restler supports tho Apple plist xml format (see require-dev for details)",
"zendframework/zendamf":"Support for the amf document format (see require-dev for details)",
"symfony/yaml":"Restler can produce content in yaml format as well (see require-dev for details)",
"twig/twig":"Restler can render HtmlView using twig templates (see require-dev for details)",
"mustache/mustache":"Restler can render HtmlView using mustache/handlebar templates (see require-dev for details)",
"bshaffer/oauth2-server-php":"Restler can provide OAuth2 authentication using this library (see require-dev for details)"
},
"require":{
"php":">=5.3.0"
},
"require-dev":{
"luracast/explorer":"*",
"rodneyrehm/plist":"dev-master",
"zendframework/zendamf":"dev-master",
"symfony/yaml":"*",
"mustache/mustache": "dev-master",
"twig/twig": "v1.13.0",
"bshaffer/oauth2-server-php":"v1.0"
},
"repositories":[
{
"type":"vcs",
"url":"https://github.com/zendframework/ZendAmf.git"
},
{
"type":"package",
"package":{
"name":"luracast/explorer",
"version":"v3.0.0",
"dist":{
"type":"zip",
"url":"https://github.com/Luracast/Restler-API-Explorer/zipball/v3.0.0"
}
}
}
],
"autoload":{
"psr-0":{
"Luracast\\Restler":""
}
},
"target-dir": "Luracast/Restler",
"replace": {
"luracast/restler":"3.*"
}
}

23
htdocs/includes/restler/composer.lock generated Normal file
View File

@ -0,0 +1,23 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ee84444dcf34101555d20a813d528c44",
"packages": [],
"packages-dev": null,
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"rodneyrehm/plist": 20,
"zendframework/zendamf": 20,
"mustache/mustache": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0"
},
"platform-dev": []
}

View File

@ -0,0 +1,25 @@
<?php
namespace Luracast\Restler;
/**
* Interface for creating authentication classes
*
* @category Framework
* @package Restler
* @subpackage auth
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iAuthenticate extends iFilter
{
/**
* @return string string to be used with WWW-Authenticate header
* @example Basic
* @example Digest
* @example OAuth
*/
public function __getWWWAuthenticateString();
}

View File

@ -0,0 +1,63 @@
<?php
namespace Luracast\Restler;
/**
* Interface for the cache system that manages caching of given data
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iCache
{
/**
* store data in the cache
*
* @abstract
*
* @param string $name
* @param mixed $data
*
* @return boolean true if successful
*/
public function set($name, $data);
/**
* retrieve data from the cache
*
* @abstract
*
* @param string $name
* @param bool $ignoreErrors
*
* @return mixed
*/
public function get($name, $ignoreErrors = false);
/**
* delete data from the cache
*
* @abstract
*
* @param string $name
* @param bool $ignoreErrors
*
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false);
/**
* check if the given name is cached
*
* @abstract
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name);
}

View File

@ -0,0 +1,36 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Interface for composing response
*
* @category Framework
* @package Restler
* @subpackage result
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iCompose {
/**
* Result of an api call is passed to this method
* to create a standard structure for the data
*
* @param mixed $result can be a primitive or array or object
*/
public function response($result);
/**
* When the api call results in RestException this method
* will be called to return the error message
*
* @param RestException $exception exception that has reasons for failure
*
* @return
*/
public function message(RestException $exception);
}

View File

@ -0,0 +1,30 @@
<?php
namespace Luracast\Restler;
/**
* Interface for creating classes that perform authentication/access
* verification
*
* @category Framework
* @package Restler
* @subpackage auth
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iFilter
{
/**
* Access verification method.
*
* API access will be denied when this method returns false
*
* @abstract
* @return boolean true when api access is allowed false otherwise
*/
public function __isAllowed();
}

View File

@ -0,0 +1,63 @@
<?php
namespace Luracast\Restler;
/**
* Interface to identify the user
*
* When the user is known we will be able to monitor, rate limit and do more
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iIdentifyUser
{
/**
* A way to uniquely identify the current api consumer
*
* When his user id is known it should be used otherwise ip address
* can be used
*
* @param bool $includePlatform Should we consider user alone or should
* consider the application/platform/device
* as well for generating unique id
*
* @return string
*/
public static function getUniqueIdentifier($includePlatform = false);
/**
* User identity to be used for caching purpose
*
* When the dynamic cache service places an object in the cache, it needs to
* label it with a unique identifying string known as a cache ID. This
* method gives that identifier
*
* @return string
*/
public static function getCacheIdentifier();
/**
* Authentication classes should call this method
*
* @param string $id user id as identified by the authentication classes
*
* @return void
*/
public static function setUniqueIdentifier($id);
/**
* User identity for caching purpose
*
* In a role based access control system this will be based on role
*
* @param $id
*
* @return void
*/
public static function setCacheIdentifier($id);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Luracast\Restler;
interface iProvideMultiVersionApi {
/**
* Maximum api version supported by the api class
* @return int
*/
public static function __getMaximumSupportedVersion();
}

View File

@ -0,0 +1,32 @@
<?php
namespace Luracast\Restler;
/**
* Api classes or filter classes can implement this interface to know about
* authentication status
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iUseAuthentication
{
/**
* This method will be called first for filter classes and api classes so
* that they can respond accordingly for filer method call and api method
* calls
*
* @abstract
*
* @param bool $isAuthenticated passes true when the authentication is
* done false otherwise
*
* @return mixed
*/
public function __setAuthenticationStatus($isAuthenticated=false);
}

View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485::getLoader();

View File

@ -0,0 +1,413 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Luracast\\Restler' => array($baseDir . '/'),
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,70 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
spl_autoload_register(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'autoload'), true, true);
$loader->register(true);
return $loader;
}
public static function autoload($class)
{
$dir = dirname(dirname(__DIR__)) . '/';
$prefixes = array('Luracast\\Restler');
foreach ($prefixes as $prefix) {
if (0 !== strpos($class, $prefix)) {
continue;
}
$path = $dir . implode('/', array_slice(explode('\\', $class), 2)).'.php';
if (!$path = stream_resolve_include_path($path)) {
return false;
}
require $path;
return true;
}
}
}
function composerRequiree65e15efc7e9ea1f2b7ba7fa697ba485($file)
{
require $file;
}

View File

@ -0,0 +1 @@
[]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,169 @@
<?php
use Luracast\Restler\Restler;
use Luracast\Restler\Util;
$template_vars = $data;//get_defined_vars();
unset($template_vars['response']);
unset($template_vars['api']);
unset($template_vars['request']);
unset($template_vars['stages']);
$template_vars['request'] = $data['request'];
$template_vars['stages'] = $data['stages'];
$call_trace = '';
function exceptions()
{
global $call_trace;
$r = Util::$restler;
$source = $r->_exceptions;
if (count($source)) {
$source = end($source);
$traces = array();
do {
$traces += $source->getTrace();
} while ($source = $source->getPrevious());
$traces += debug_backtrace();
$call_trace
= parse_backtrace($traces, 0);
} else {
$call_trace
= parse_backtrace(debug_backtrace());
}
}
exceptions();
function parse_backtrace($raw, $skip = 1)
{
$output = "";
foreach ($raw as $entry) {
if ($skip-- > 0) {
continue;
}
//$output .= print_r($entry, true) . "\n";
$output .= "\nFile: " . $entry['file'] . " (Line: " . $entry['line'] . ")\n";
if (isset($entry['class']))
$output .= $entry['class'] . "::";
$output .= $entry['function']
. "( " . json_encode($entry['args']) . " )\n";
}
return $output;
}
//print_r(get_defined_vars());
//print_r($response);
$icon;
if ($success && isset($api)) {
$arguments = implode(', ', $api->parameters);
$icon = "<icon class=\"success\"></icon>";
$title = "{$api->className}::"
. "{$api->methodName}({$arguments})";
} else {
if (isset($response['error']['message'])) {
$icon = '<icon class="denied"></icon>';
$title = end(explode(':',$response['error']['message']));
} else {
$icon = '<icon class="warning"></icon>';
$title = 'No Matching Resource';
}
}
function render($data, $shadow=true)
{
$r = '';
if (empty($data))
return $r;
$r .= $shadow ? "<ul class=\"shadow\">\n": "<ul>\n";
if (is_array($data)) {
// field name
foreach ($data as $key => $value) {
$r .= '<li>';
$r .= is_numeric($key)
? "<strong>[$key]</strong> "
: "<strong>$key: </strong>";
$r .= '<span>';
if (is_array($value)) {
// recursive
$r .= render($value,false);
} else {
// value, with hyperlinked hyperlinks
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
$value = htmlentities($value, ENT_COMPAT, 'UTF-8');
if (strpos($value, 'http://') === 0) {
$r .= '<a href="' . $value . '">' . $value . '</a>';
} else {
$r .= $value;
}
}
$r .= "</span></li>\n";
}
} elseif (is_bool($data)) {
$r .= '<li>' . ($data ? 'true' : 'false') . '</li>';
} else {
$r .= "<li><strong>$data</strong></li>";
}
$r .= "</ul>\n";
return $r;
}
$reqHeadersArr = array();
$requestHeaders = $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . ' ' . $_SERVER['SERVER_PROTOCOL'] . PHP_EOL;
foreach ($reqHeadersArr as $key => $value) {
if ($key == 'Host')
continue;
$requestHeaders .= "$key: $value" . PHP_EOL;
}
// $requestHeaders = $this->encode(apache_request_headers(), FALSE,
// FALSE);
$responseHeaders = implode(PHP_EOL, headers_list()).PHP_EOL.'Status: HTTP/1.1 ';
$responseHeaders .= Util::$restler->responseCode.' '.\Luracast\Restler\RestException::$codes[Util::$restler->responseCode];
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $title?></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
<?php include __DIR__.'/debug.css'; ?>
</style>
</head>
<body>
<div id="breadcrumbs-one">
<?php
if(Util::$restler->exception){
$stages = Util::$restler->exception->getStages();
$curStage = Util::$restler->exception->getStage();
foreach($stages['success'] as $stage){
echo "<a href=\"#\">$stage</a>";
}
foreach($stages['failure'] as $stage){
echo '<a href="#" class="failure">'
. $stage
. ($stage==$curStage ? ' <span class="state"/> ' : '')
. '</a>';
}
} else {
foreach(Util::$restler->_events as $stage){
echo "<a href=\"#\">$stage</a>";
}
}
?>
</div>
<header>
<h1><?php echo $title ?></h1>
</header>
<article>
<h2>Response:<right><?php echo $icon;?></right></h2>
<pre class="header"><?php echo $responseHeaders ?></pre>
<?php echo render($response); ?>
<h2>Additional Template Data:</h2>
<?php echo render($template_vars); ?>
<p>Restler v<?php echo Restler::VERSION?></p>
</article>
</body>
</html>

View File

@ -85,7 +85,7 @@ ALTER TABLE llx_projet_task MODIFY COLUMN duration_effective real DEFAULT 0 NULL
ALTER TABLE llx_projet_task MODIFY COLUMN planned_workload real DEFAULT 0 NULL;
ALTER TABLE llx_commande_fournisseur MODIFY COLUMN date_livraison datetime;
ALTER TABLE llx_commande_fournisseur MODIFY COLUMN date_livraison datetime;
-- Add id commandefourndet in llx_commande_fournisseur_dispatch to correct /fourn/commande/dispatch.php display when several times same product in supplier order
ALTER TABLE llx_commande_fournisseur_dispatch ADD COLUMN fk_commandefourndet INTEGER NOT NULL DEFAULT 0 AFTER fk_product;
@ -112,12 +112,12 @@ ALTER TABLE llx_product_price ADD COLUMN fk_price_expression integer DEFAULT NUL
--create table for user conf of printing driver
CREATE TABLE llx_printing
CREATE TABLE llx_printing
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
tms timestamp,
datec datetime,
printer_name text NOT NULL,
printer_name text NOT NULL,
printer_location text NOT NULL,
printer_id varchar(255) NOT NULL,
copy integer NOT NULL DEFAULT '1',
@ -199,7 +199,7 @@ CREATE TABLE llx_expensereport (
total_ht double(24,8) DEFAULT 0,
total_tva double(24,8) DEFAULT 0,
localtax1 double(24,8) DEFAULT 0, -- amount total localtax1
localtax2 double(24,8) DEFAULT 0, -- amount total localtax2
localtax2 double(24,8) DEFAULT 0, -- amount total localtax2
total_ttc double(24,8) DEFAULT 0,
date_debut date NOT NULL,
date_fin date NOT NULL,
@ -292,11 +292,11 @@ ALTER TABLE llx_commande_fournisseurdet ADD COLUMN special_code integer DEFAULT
ALTER TABLE llx_commande_fournisseurdet ADD COLUMN rang integer DEFAULT 0;
ALTER TABLE llx_commande_fournisseurdet ADD COLUMN fk_parent_line integer NULL after fk_commande;
ALTER TABLE llx_projet ADD COLUMN date_close datetime DEFAULT NULL;
ALTER TABLE llx_projet ADD COLUMN date_close datetime DEFAULT NULL;
ALTER TABLE llx_projet ADD COLUMN fk_user_close integer DEFAULT NULL;
-- Module AskPriceSupplier --
CREATE TABLE llx_askpricesupplier (
rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
@ -602,6 +602,12 @@ ALTER TABLE llx_user DROP INDEX idx_user_fk_societe;
ALTER TABLE llx_user CHANGE COLUMN fk_societe fk_soc INTEGER;
ALTER TABLE llx_user ADD INDEX idx_user_fk_societe (fk_soc);
-- API module
ALTER TABLE llx_user ADD api_key VARCHAR(128) DEFAULT NULL AFTER pass_temp;
ALTER TABLE llx_user ADD INDEX idx_user_api_key (api_key);
ALTER TABLE llx_actioncomm ADD COLUMN email_msgid varchar(256);
ALTER TABLE llx_actioncomm ADD COLUMN email_from varchar(256);
ALTER TABLE llx_actioncomm ADD COLUMN email_sender varchar(256);

View File

@ -25,3 +25,4 @@ ALTER TABLE llx_user ADD INDEX idx_user_fk_societe (fk_soc);
ALTER TABLE llx_user ADD UNIQUE INDEX uk_user_fk_socpeople (fk_socpeople);
ALTER TABLE llx_user ADD UNIQUE INDEX uk_user_fk_member (fk_member);
ALTER TABLE llx_user ADD UNIQUE INDEX uk_user_api_key (api_key);

View File

@ -34,6 +34,7 @@ create table llx_user
pass varchar(32),
pass_crypted varchar(128),
pass_temp varchar(32), -- temporary password when asked for forget password
api_key varchar(128),
civility varchar(6),
lastname varchar(50),
firstname varchar(50),

View File

@ -526,6 +526,8 @@ Module2500Name=Electronic Content Management
Module2500Desc=Save and share documents
Module2600Name=WebServices
Module2600Desc=Enable the Dolibarr web services server
Module2610Name=Api
Module2610Desc=Enable the Dolibarr REST API
Module2650Name=WebServices (client)
Module2650Desc=Enable the Dolibarr web services client (Can be used to push data/requests to external servers. Supplier orders supported only for the moment)
Module2700Name=Gravatar
@ -1558,6 +1560,13 @@ WebServicesSetup=Webservices module setup
WebServicesDesc=By enabling this module, Dolibarr become a web service server to provide miscellaneous web services.
WSDLCanBeDownloadedHere=WSDL descriptor files of provided services can be download here
EndPointIs=SOAP clients must send their requests to the Dolibarr endpoint available at Url
##### API ####
ApiSetup=API module setup
ApiDesc=By enabling this module, Dolibarr become a REST server to provide miscellaneous web services.
KeyForApiAccess=Key to use API (parameter "api_key")
ApiEndPointIs=You can access to the API at url
ApiExporerIs=You can explore the API at url
OnlyActiveElementsAreExposed=Only elements from enabled modules are exposed
##### Bank #####
BankSetupModule=Bank module setup
FreeLegalTextOnChequeReceipts=Free text on cheque receipts

View File

@ -0,0 +1,273 @@
<?php
/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
/**
* API class for product object
*
* @smart-auto-routing false
* @access protected
* @class DolibarrApiAccess {@requires user,external}
*
*/
class ProductApi extends DolibarrApi
{
/**
* @var array $FIELDS Mandatory fields, checked when create and update object
*/
static $FIELDS = array(
'ref',
'label'
);
/**
* @var Product $product {@type Product}
*/
public $product;
/**
* Constructor
*
* @url product/
*
*/
function __construct()
{
global $db, $conf;
$this->db = $db;
$this->product = new Product($this->db);
}
/**
* Get properties of a product object
*
* Return an array with product informations
*
* @param int $id ID of product
* @param string $ref Product ref
* @param string $ref_ext Product ref ext
* @return array|mixed data without useless information
*
* @url GET product/{id}
* @throws RestException
*/
function get($id='', $ref='', $ref_ext='')
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$result = $this->product->fetch($id,$ref,$ref_ext);
if( ! $result ) {
throw new RestException(404, 'Product not found');
}
if( ! DolibarrApi::_checkAccessToResource('product',$this->product->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$this->product->load_stock();
return $this->_cleanObjectDatas($this->product);
}
/**
* List products
*
* Get a list of products
*
* @param int $mode Use this param to filter list (0 for all, 1 for only product, 2 for only service)
* @param mixed $to_sell Filter products to sell (1) or not to sell (0)
* @param mixed $to_buy Filter products to nuy (1) or not to buy (0)
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $limit Limit for list
* @param int $page Page number
*
* @return array Array of product objects
*
* @url GET /product/list
*/
function getList($mode=0, $to_sell='', $to_buy='', $sortfield = "p.ref", $sortorder = 'ASC', $limit = 0, $page = 0) {
global $db, $conf;
$obj_ret = array();
$socid = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : '';
$sql ="SELECT rowid, ref, ref_ext";
$sql.= " FROM ".MAIN_DB_PREFIX."product as p";
$sql.= ' WHERE p.entity IN ('.getEntity('product', 1).')';
// Show products
if ($mode == 1) $sql.= " AND p.fk_product_type = 0";
// Show services
if ($mode == 2) $sql.= " AND p.fk_product_type = 1";
// Show product on sell
if ($to_sell) $sql.= " AND p.to_sell = ".$db->escape($to_sell);
// Show product on buy
if ($to_buy) $sql.= " AND p.to_nuy = ".$db->escape($to_nuy);
$nbtotalofrecords = 0;
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
{
$result = $db->query($sql);
$nbtotalofrecords = $db->num_rows($result);
}
$sql.= $db->order($sortfield, $sortorder);
if ($limit) {
if ($page < 0)
{
$page = 0;
}
$offset = $limit * $page;
$sql.= $db->plimit($limit + 1, $offset);
}
$result = $db->query($sql);
if ($result)
{
$num = $db->num_rows($result);
while ($i < $num)
{
$obj = $db->fetch_object($result);
$product_static = new Product($db);
if($product_static->fetch($obj->rowid)) {
$obj_ret[] = parent::_cleanObjectDatas($product_static);
}
$i++;
}
}
else {
throw new RestException(503, 'Error when retrieve product list');
}
if( ! count($obj_ret)) {
throw new RestException(404, 'No product found');
}
return $obj_ret;
}
/**
* Create product object
*
* @param array $request_data Request data
* @return int ID of product
*
* @url POST product/
*/
function post($request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
// Check mandatory fields
$result = $this->_validate($request_data);
foreach($request_data as $field => $value) {
$this->product->$field = $value;
}
$result = $this->product->create(DolibarrApiAccess::$user);
if($result < 0) {
throw new RestException(503,'Error when creating product : '.$this->product->error);
}
return $this->product->id;
}
/**
* Update product
*
* @param int $id Id of product to update
* @param array $request_data Datas
* @return int
*
* @url PUT product/{id}
*/
function put($id, $request_data = NULL)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
$result = $this->product->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Product not found');
}
if( ! DolibarrApi::_checkAccessToResource('product',$this->product->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
foreach($request_data as $field => $value) {
$this->product->$field = $value;
}
if($this->product->update($id, DolibarrApiAccess::$user,1,'','','update'))
return $this->get ($id);
return false;
}
/**
* Delete product
*
* @param int $id Product ID
* @return array
*
* @url DELETE product/{id}
*/
function delete($id)
{
if(! DolibarrApiAccess::$user->rights->product->supprimer) {
throw new RestException(401);
}
$result = $this->product->fetch($id);
if( ! $result ) {
throw new RestException(404, 'Product not found');
}
if( ! DolibarrApi::_checkAccessToResource('product',$this->product->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
return $this->product->delete($id);
}
/**
* Validate fields before create or update object
*
* @param array $data Datas to validate
* @return array
* @throws RestException
*/
function _validate($data)
{
$product = array();
foreach (ProductApi::$FIELDS as $field) {
if (!isset($data[$field]))
throw new RestException(400, "$field field missing");
$product[$field] = $data[$field];
}
return $product;
}
}

View File

@ -0,0 +1,15 @@
#
# Apache configuration file to use API
#
DirectoryIndex index.php
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^$ index.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c>
php_flag display_errors On
</IfModule>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,95 @@
<html>
<head>
<title>Restler API Explorer</title>
<link href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'/>
<link href="http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='lib/handlebars-1.0.rc.1.js' type='text/javascript'></script>
<script src='lib/underscore-min.js' type='text/javascript'></script>
<script src='lib/backbone-min.js' type='text/javascript'></script>
<script src='lib/swagger.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script>
<style type="text/css">
.swagger-ui-wrap {
max-width: 960px;
margin-left: auto;
margin-right: auto;
}
.icon-btn {
cursor: pointer;
}
#message-bar {
min-height: 30px;
text-align: center;
padding-top: 10px;
}
.message-success {
color: #89BF04;
}
.message-fail {
color: #cc0000;
}
</style>
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
discoveryUrl:"../resources.json",
apiKey:"",
dom_id:"swagger-ui-container",
//supportHeaderParams: true,
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
if(console) {
console.log("Loaded SwaggerUI")
console.log(swaggerApi);
console.log(swaggerUi);
}
},
onFailure: function(data) {
if(console) {
console.log("Unable to Load SwaggerUI");
console.log(data);
}
},
docExpansion: "none"
});
window.swaggerUi.load();
});
</script>
</head>
<body>
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="https://github.com/Luracast/Restler-API-Explorer" target="_blank">API Explorer</a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="hidden"/></div>
<div class='input'><input placeholder="api_key" id="input_apiKey" name="apiKey" type="text"/></div>
<div class='input'><a id="explore" href="#">Explore</a></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap">
&nbsp;
</div>
<div id="swagger-ui-container" class="swagger-ui-wrap">
</div>
</body>
</html>

View File

@ -0,0 +1,38 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,223 @@
// lib/handlebars/base.js
var Handlebars = {};
Handlebars.VERSION = "1.0.beta.6";
Handlebars.helpers = {};
Handlebars.partials = {};
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
}
});
var toString = Object.prototype.toString, functionType = "[object Function]";
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
var ret = "";
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
}
} else {
ret = inverse(this);
}
return ret;
} else {
return fn(context);
}
});
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "";
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
}
} else {
ret = inverse(this);
}
return ret;
});
Handlebars.registerHelper('if', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if(!context || Handlebars.Utils.isEmpty(context)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(context, options) {
var fn = options.fn, inverse = options.inverse;
options.fn = inverse;
options.inverse = fn;
return Handlebars.helpers['if'].call(this, context, options);
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
});
Handlebars.registerHelper('log', function(context) {
Handlebars.log(context);
});
;
// lib/handlebars/utils.js
Handlebars.Exception = function(message) {
var tmp = Error.prototype.constructor.apply(this, arguments);
for (var p in tmp) {
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
}
this.message = tmp.message;
};
Handlebars.Exception.prototype = new Error;
// Build out our basic SafeString type
Handlebars.SafeString = function(string) {
this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
(function() {
var escape = {
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /&(?!\w+;)|[<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
Handlebars.Utils = {
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (typeof value === "undefined") {
return true;
} else if (value === null) {
return true;
} else if (value === false) {
return true;
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
return true;
} else {
return false;
}
}
};
})();;
// lib/handlebars/runtime.js
Handlebars.VM = {
template: function(templateSpec) {
// Just add water
var container = {
escapeExpression: Handlebars.Utils.escapeExpression,
invokePartial: Handlebars.VM.invokePartial,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
return Handlebars.VM.program(fn, data);
} else if(programWrapper) {
return programWrapper;
} else {
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
return programWrapper;
}
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop
};
return function(context, options) {
options = options || {};
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
};
},
programWithDepth: function(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
return function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
},
program: function(fn, data) {
return function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
options = { helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
} else if (!Handlebars.compile) {
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
} else {
partials[name] = Handlebars.compile(partial);
return partials[name](context, options);
}
}
};
Handlebars.template = Handlebars.VM.template;
;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);

View File

@ -0,0 +1 @@
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);

View File

@ -0,0 +1,8 @@
/*
jQuery Wiggle
Author: WonderGroup, Jordan Thomas
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
*/
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};

View File

@ -0,0 +1,694 @@
// Generated by CoffeeScript 1.3.3
(function() {
var SwaggerApi, SwaggerModel, SwaggerModelProperty, SwaggerOperation, SwaggerRequest, SwaggerResource,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
SwaggerApi = (function() {
SwaggerApi.prototype.discoveryUrl = "http://api.wordnik.com/v4/resources.json";
SwaggerApi.prototype.debug = false;
SwaggerApi.prototype.api_key = null;
SwaggerApi.prototype.basePath = null;
function SwaggerApi(options) {
if (options == null) {
options = {};
}
if (options.discoveryUrl != null) {
this.discoveryUrl = options.discoveryUrl;
}
if (options.debug != null) {
this.debug = options.debug;
}
this.apiKeyName = options.apiKeyName != null ? options.apiKeyName : 'api_key';
if (options.apiKey != null) {
this.api_key = options.apiKey;
}
if (options.api_key != null) {
this.api_key = options.api_key;
}
if (options.verbose != null) {
this.verbose = options.verbose;
}
this.supportHeaderParams = options.supportHeaderParams != null ? options.supportHeaderParams : false;
this.supportedSubmitMethods = options.supportedSubmitMethods != null ? options.supportedSubmitMethods : ['get'];
if (options.success != null) {
this.success = options.success;
}
this.failure = options.failure != null ? options.failure : function() {};
this.progress = options.progress != null ? options.progress : function() {};
this.headers = options.headers != null ? options.headers : {};
this.booleanValues = options.booleanValues != null ? options.booleanValues : new Array('true', 'false');
this.discoveryUrl = this.suffixApiKey(this.discoveryUrl);
if (options.success != null) {
this.build();
}
}
SwaggerApi.prototype.build = function() {
var _this = this;
this.progress('fetching resource list: ' + this.discoveryUrl);
return jQuery.getJSON(this.discoveryUrl, function(response) {
var res, resource, _i, _j, _len, _len1, _ref, _ref1;
if ((response.basePath != null) && jQuery.trim(response.basePath).length > 0) {
_this.basePath = response.basePath;
if (_this.basePath.match(/^HTTP/i) == null) {
_this.fail("discoveryUrl basePath must be a URL.");
}
_this.basePath = _this.basePath.replace(/\/$/, '');
} else {
_this.basePath = _this.discoveryUrl.substring(0, _this.discoveryUrl.lastIndexOf('/'));
log('derived basepath from discoveryUrl as ' + _this.basePath);
}
_this.resources = {};
_this.resourcesArray = [];
if (response.resourcePath != null) {
_this.resourcePath = response.resourcePath;
res = null;
_ref = response.apis;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
resource = _ref[_i];
if (res === null) {
res = new SwaggerResource(resource, _this);
} else {
res.addOperations(resource.path, resource.operations);
}
}
if (res != null) {
_this.resources[res.name] = res;
_this.resourcesArray.push(res);
res.ready = true;
_this.selfReflect();
}
} else {
_ref1 = response.apis;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
resource = _ref1[_j];
res = new SwaggerResource(resource, _this);
_this.resources[res.name] = res;
_this.resourcesArray.push(res);
}
}
return _this;
}).error(function(error) {
return _this.fail(error.status + ' : ' + error.statusText + ' ' + _this.discoveryUrl);
});
};
SwaggerApi.prototype.selfReflect = function() {
var resource, resource_name, _ref;
if (this.resources == null) {
return false;
}
_ref = this.resources;
for (resource_name in _ref) {
resource = _ref[resource_name];
if (resource.ready == null) {
return false;
}
}
this.setConsolidatedModels();
this.ready = true;
if (this.success != null) {
return this.success();
}
};
SwaggerApi.prototype.fail = function(message) {
this.failure(message);
throw message;
};
SwaggerApi.prototype.setConsolidatedModels = function() {
var model, modelName, resource, resource_name, _i, _len, _ref, _ref1, _results;
this.modelsArray = [];
this.models = {};
_ref = this.resources;
for (resource_name in _ref) {
resource = _ref[resource_name];
for (modelName in resource.models) {
if (!(this.models[modelName] != null)) {
this.models[modelName] = resource.models[modelName];
this.modelsArray.push(resource.models[modelName]);
}
}
}
_ref1 = this.modelsArray;
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
model = _ref1[_i];
_results.push(model.setReferencedModels(this.models));
}
return _results;
};
SwaggerApi.prototype.suffixApiKey = function(url) {
var sep;
if ((this.api_key != null) && jQuery.trim(this.api_key).length > 0 && (url != null)) {
sep = url.indexOf('?') > 0 ? '&' : '?';
return url + sep + this.apiKeyName + '=' + this.api_key;
} else {
return url;
}
};
SwaggerApi.prototype.help = function() {
var operation, operation_name, parameter, resource, resource_name, _i, _len, _ref, _ref1, _ref2;
_ref = this.resources;
for (resource_name in _ref) {
resource = _ref[resource_name];
console.log(resource_name);
_ref1 = resource.operations;
for (operation_name in _ref1) {
operation = _ref1[operation_name];
console.log(" " + operation.nickname);
_ref2 = operation.parameters;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
parameter = _ref2[_i];
console.log(" " + parameter.name + (parameter.required ? ' (required)' : '') + " - " + parameter.description);
}
}
}
return this;
};
return SwaggerApi;
})();
SwaggerResource = (function() {
function SwaggerResource(resourceObj, api) {
var parts,
_this = this;
this.api = api;
this.path = this.api.resourcePath != null ? this.api.resourcePath : resourceObj.path;
this.description = resourceObj.description;
parts = this.path.split("/");
this.name = parts[parts.length - 1].replace('.{format}', '');
this.basePath = this.api.basePath;
this.operations = {};
this.operationsArray = [];
this.modelsArray = [];
this.models = {};
if ((resourceObj.operations != null) && (this.api.resourcePath != null)) {
this.api.progress('reading resource ' + this.name + ' models and operations');
this.addModels(resourceObj.models);
this.addOperations(resourceObj.path, resourceObj.operations);
this.api[this.name] = this;
} else {
if (this.path == null) {
this.api.fail("SwaggerResources must have a path.");
}
this.url = this.api.suffixApiKey(this.api.basePath + this.path.replace('{format}', 'json'));
this.api.progress('fetching resource ' + this.name + ': ' + this.url);
jQuery.getJSON(this.url, function(response) {
var endpoint, _i, _len, _ref;
if ((response.basePath != null) && jQuery.trim(response.basePath).length > 0) {
_this.basePath = response.basePath;
_this.basePath = _this.basePath.replace(/\/$/, '');
}
_this.addModels(response.models);
if (response.apis) {
_ref = response.apis;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
endpoint = _ref[_i];
_this.addOperations(endpoint.path, endpoint.operations);
}
}
_this.api[_this.name] = _this;
_this.ready = true;
return _this.api.selfReflect();
}).error(function(error) {
return _this.api.fail(error.status + ' : ' + error.statusText + ' ' + _this.url);
});
}
}
SwaggerResource.prototype.addModels = function(models) {
var model, modelName, swaggerModel, _i, _len, _ref, _results;
if (models != null) {
for (modelName in models) {
swaggerModel = new SwaggerModel(modelName, models[modelName]);
this.modelsArray.push(swaggerModel);
this.models[modelName] = swaggerModel;
}
_ref = this.modelsArray;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
model = _ref[_i];
_results.push(model.setReferencedModels(this.models));
}
return _results;
}
};
SwaggerResource.prototype.addOperations = function(resource_path, ops) {
var o, op, _i, _len, _results;
if (ops) {
_results = [];
for (_i = 0, _len = ops.length; _i < _len; _i++) {
o = ops[_i];
op = new SwaggerOperation(o.nickname, resource_path, o.httpMethod, o.parameters, o.summary, o.notes, o.responseClass, this);
this.operations[op.nickname] = op;
_results.push(this.operationsArray.push(op));
}
return _results;
}
};
SwaggerResource.prototype.help = function() {
var operation, operation_name, parameter, _i, _len, _ref, _ref1;
_ref = this.operations;
for (operation_name in _ref) {
operation = _ref[operation_name];
console.log(" " + operation.nickname);
_ref1 = operation.parameters;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
parameter = _ref1[_i];
console.log(" " + parameter.name + (parameter.required ? ' (required)' : '') + " - " + parameter.description);
}
}
return this;
};
return SwaggerResource;
})();
SwaggerModel = (function() {
function SwaggerModel(modelName, obj) {
var propertyName;
this.name = obj.id != null ? obj.id : modelName;
this.properties = [];
for (propertyName in obj.properties) {
this.properties.push(new SwaggerModelProperty(propertyName, obj.properties[propertyName]));
}
}
SwaggerModel.prototype.setReferencedModels = function(allModels) {
var prop, _i, _len, _ref, _results;
_ref = this.properties;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
prop = _ref[_i];
if (allModels[prop.dataType] != null) {
_results.push(prop.refModel = allModels[prop.dataType]);
} else if ((prop.refDataType != null) && (allModels[prop.refDataType] != null)) {
_results.push(prop.refModel = allModels[prop.refDataType]);
} else {
_results.push(void 0);
}
}
return _results;
};
SwaggerModel.prototype.getMockSignature = function(prefix, modelToIgnore) {
var classClose, classOpen, prop, propertiesStr, returnVal, strong, strongClose, stronger, _i, _j, _len, _len1, _ref, _ref1;
propertiesStr = [];
_ref = this.properties;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
prop = _ref[_i];
propertiesStr.push(prop.name+': <tag>'+prop.dataType+'</tag>');
}
strong = '<span style="font-weight: bold; color: #000; font-size: 1.0em">';
stronger = '<span style="font-weight: bold; color: #000; font-size: 1.1em">';
strongClose = '</span>';
classOpen = strong + ' ' + (prefix != null? prefix : this.name) + ' ( ' + strongClose;
classClose = strong + ' )' + strongClose;
returnVal = classOpen + '<span>' + propertiesStr.join('</span>, <span>') + '</span>' + classClose;
_ref1 = this.properties;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
prop = _ref1[_j];
if ((prop.refModel != null) && (!(prop.refModel === modelToIgnore))) {
returnVal = returnVal + ('<br>' + prop.refModel.getMockSignature(void 0, this));
}
}
return returnVal;
};
return SwaggerModel;
})();
SwaggerModelProperty = (function() {
function SwaggerModelProperty(name, obj) {
this.name = name;
this.dataType = obj.type;
this.isArray = this.dataType.toLowerCase() === 'array';
this.descr = obj.description;
if (obj.items != null) {
if (obj.items.type != null) {
this.refDataType = obj.items.type;
}
if (obj.items.$ref != null) {
this.refDataType = obj.items.$ref;
}
}
this.dataTypeWithRef = this.refDataType != null ? this.dataType + '[' + this.refDataType + ']' : this.dataType;
if (obj.allowableValues != null) {
this.valueType = obj.allowableValues.valueType;
this.values = obj.allowableValues.values;
if (this.values != null) {
this.valuesString = "'" + this.values.join("' or '") + "'";
}
}
}
SwaggerModelProperty.prototype.toString = function() {
var str;
str = this.name + ': ' + this.dataTypeWithRef;
if (this.values != null) {
str += " = ['" + this.values.join("' or '") + "']";
}
if (this.descr != null) {
str += ' {' + this.descr + '}';
}
return str;
};
return SwaggerModelProperty;
})();
SwaggerOperation = (function() {
function SwaggerOperation(nickname, path, httpMethod, parameters, summary, notes, responseClass, resource) {
var parameter, v, _i, _j, _len, _len1, _ref, _ref1,
_this = this;
this.nickname = nickname;
this.path = path;
this.httpMethod = httpMethod;
this.parameters = parameters != null ? parameters : [];
this.summary = summary;
this.notes = notes;
this.responseClass = responseClass;
this.resource = resource;
this["do"] = __bind(this["do"], this);
if (this.nickname == null) {
this.resource.api.fail("SwaggerOperations must have a nickname.");
}
if (this.path == null) {
this.resource.api.fail("SwaggerOperation " + nickname + " is missing path.");
}
if (this.httpMethod == null) {
this.resource.api.fail("SwaggerOperation " + nickname + " is missing httpMethod.");
}
this.path = this.path.replace('{format}', 'json');
this.httpMethod = this.httpMethod.toLowerCase();
this.isGetMethod = this.httpMethod === "get";
this.resourceName = this.resource.name;
if (this.responseClass.toLowerCase() === 'void') {
this.responseClass = void 0;
}
if (this.responseClass != null) {
this.responseClassSignature = this.getSignature(this.responseClass, this.resource.models);
}
_ref = this.parameters;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
parameter = _ref[_i];
parameter.name = parameter.name || parameter.dataType;
if (parameter.dataType.toLowerCase() === 'boolean') {
parameter.allowableValues = {};
parameter.allowableValues.values = this.resource.api.booleanValues;
}
parameter.signature = this.getSignature(parameter.dataType, this.resource.models);
if (parameter.allowableValues != null) {
if (parameter.allowableValues.valueType === "RANGE") {
parameter.isRange = true;
} else {
parameter.isList = true;
}
if (parameter.allowableValues.values != null) {
parameter.allowableValues.descriptiveValues = [];
_ref1 = parameter.allowableValues.values;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
v = _ref1[_j];
if ((parameter.defaultValue != null) && parameter.defaultValue === v) {
parameter.allowableValues.descriptiveValues.push({
value: v,
isDefault: true
});
} else {
parameter.allowableValues.descriptiveValues.push({
value: v,
isDefault: false
});
}
}
}
}
}
this.resource[this.nickname] = function(args, callback, error) {
return _this["do"](args, callback, error);
};
}
SwaggerOperation.prototype.isListType = function(dataType) {
if (dataType.indexOf('[') >= 0) {
return dataType.substring(dataType.indexOf('[') + 1, dataType.indexOf(']'));
} else {
return void 0;
}
};
SwaggerOperation.prototype.getSignature = function(dataType, models) {
var isPrimitive, listType;
listType = this.isListType(dataType);
isPrimitive = ((listType != null) && models[listType]) || (models[dataType] != null) ? false : true;
if (isPrimitive) {
return dataType;
} else {
if (listType != null) {
return models[listType].getMockSignature(dataType);
} else {
return models[dataType].getMockSignature(dataType);
}
}
};
SwaggerOperation.prototype["do"] = function(args, callback, error) {
var body, headers;
if (args == null) {
args = {};
}
if ((typeof args) === "function") {
error = callback;
callback = args;
args = {};
}
if (error == null) {
error = function(xhr, textStatus, error) {
return console.log(xhr, textStatus, error);
};
}
if (callback == null) {
callback = function(data) {
return console.log(data);
};
}
if (args.headers != null) {
headers = args.headers;
delete args.headers;
}
if (args.body != null) {
body = args.body;
delete args.body;
}
return new SwaggerRequest(this.httpMethod, this.urlify(args), headers, body, callback, error, this);
};
SwaggerOperation.prototype.pathJson = function() {
return this.path.replace("{format}", "json");
};
SwaggerOperation.prototype.pathXml = function() {
return this.path.replace("{format}", "xml");
};
SwaggerOperation.prototype.urlify = function(args, includeApiKey) {
var param, queryParams, url, _i, _len, _ref;
if (includeApiKey == null) {
includeApiKey = true;
}
url = this.resource.basePath + this.pathJson();
_ref = this.parameters;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
param = _ref[_i];
if (param.paramType === 'path') {
if (args[param.name]) {
url = url.replace("{" + param.name + "}", encodeURIComponent(args[param.name]));
delete args[param.name];
} else {
throw "" + param.name + " is a required path param.";
}
}
}
if (includeApiKey && (this.resource.api.api_key != null) && this.resource.api.api_key.length > 0) {
args[this.apiKeyName] = this.resource.api.api_key;
}
if (this.supportHeaderParams()) {
queryParams = jQuery.param(this.getQueryParams(args, includeApiKey));
} else {
queryParams = jQuery.param(this.getQueryAndHeaderParams(args, includeApiKey));
}
if ((queryParams != null) && queryParams.length > 0) {
url += "?" + queryParams;
}
return url;
};
SwaggerOperation.prototype.supportHeaderParams = function() {
return this.resource.api.supportHeaderParams;
};
SwaggerOperation.prototype.supportedSubmitMethods = function() {
return this.resource.api.supportedSubmitMethods;
};
SwaggerOperation.prototype.getQueryAndHeaderParams = function(args, includeApiKey) {
if (includeApiKey == null) {
includeApiKey = true;
}
return this.getMatchingParams(['query', 'header'], args, includeApiKey);
};
SwaggerOperation.prototype.getQueryParams = function(args, includeApiKey) {
if (includeApiKey == null) {
includeApiKey = true;
}
return this.getMatchingParams(['query'], args, includeApiKey);
};
SwaggerOperation.prototype.getHeaderParams = function(args, includeApiKey) {
if (includeApiKey == null) {
includeApiKey = true;
}
return this.getMatchingParams(['header'], args, includeApiKey);
};
SwaggerOperation.prototype.getMatchingParams = function(paramTypes, args, includeApiKey) {
var matchingParams, name, param, value, _i, _len, _ref, _ref1;
matchingParams = {};
_ref = this.parameters;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
param = _ref[_i];
if ((jQuery.inArray(param.paramType, paramTypes) >= 0) && args[param.name]) {
matchingParams[param.name] = args[param.name];
}
}
if (includeApiKey && (this.resource.api.api_key != null) && this.resource.api.api_key.length > 0) {
matchingParams[this.resource.api.apiKeyName] = this.resource.api.api_key;
}
if (jQuery.inArray('header', paramTypes) >= 0) {
_ref1 = this.resource.api.headers;
for (name in _ref1) {
value = _ref1[name];
matchingParams[name] = value;
}
}
return matchingParams;
};
SwaggerOperation.prototype.help = function() {
var parameter, _i, _len, _ref;
_ref = this.parameters;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
parameter = _ref[_i];
console.log(" " + parameter.name + (parameter.required ? ' (required)' : '') + " - " + parameter.description);
}
return this;
};
return SwaggerOperation;
})();
SwaggerRequest = (function() {
function SwaggerRequest(type, url, headers, body, successCallback, errorCallback, operation) {
var obj,
_this = this;
this.type = type;
this.url = url;
this.headers = headers;
this.body = body;
this.successCallback = successCallback;
this.errorCallback = errorCallback;
this.operation = operation;
if (this.type == null) {
throw "SwaggerRequest type is required (get/post/put/delete).";
}
if (this.url == null) {
throw "SwaggerRequest url is required.";
}
if (this.successCallback == null) {
throw "SwaggerRequest successCallback is required.";
}
if (this.errorCallback == null) {
throw "SwaggerRequest error callback is required.";
}
if (this.operation == null) {
throw "SwaggerRequest operation is required.";
}
if (this.operation.resource.api.verbose) {
console.log(this.asCurl());
}
this.headers || (this.headers = {});
if (this.operation.resource.api.api_key != null) {
this.headers[this.apiKeyName] = this.operation.resource.api.api_key;
}
if (this.headers.mock == null) {
obj = {
type: this.type,
url: this.url,
data: JSON.stringify(this.body),
dataType: 'json',
error: function(xhr, textStatus, error) {
return _this.errorCallback(xhr, textStatus, error);
},
success: function(data) {
return _this.successCallback(data);
}
};
if (obj.type.toLowerCase() === "post" || obj.type.toLowerCase() === "put") {
obj.contentType = "application/json";
}
jQuery.ajax(obj);
}
}
SwaggerRequest.prototype.asCurl = function() {
var header_args, k, v;
header_args = (function() {
var _ref, _results;
_ref = this.headers;
_results = [];
for (k in _ref) {
v = _ref[k];
_results.push("--header \"" + k + ": " + v + "\"");
}
return _results;
}).call(this);
return "curl " + (header_args.join(" ")) + " " + this.url;
};
return SwaggerRequest;
})();
window.SwaggerApi = SwaggerApi;
window.SwaggerResource = SwaggerResource;
window.SwaggerOperation = SwaggerOperation;
window.SwaggerRequest = SwaggerRequest;
}).call(this);

Some files were not shown because too many files have changed in this diff Show More