* @license PHP License
* @package WB
* @subpackage service
*/
WBClass::load('WBService'
, 'WBHtml'
, 'WBHtml_JS');
/**
* Process AJAX request
*
* @todo move actual runner methods to command classes
* @version 0.4.2
* @package WB
* @subpackage service
*/
class WBService_Ajax extends WBService
{
/**
* Display
*
* @param int $status
* @param mixed $data to be send
* @param string $checksum
* @return bool true
*/
public function run(&$status, &$data, &$checksum = null)
{
// switch off caching
$this->res->useCache(false);
$this->res->setMaxAge(null);
if ($this->useI18n) {
$this->res->addHeader('Content-Language', patI18n::getLocale(patI18n::LOCALE_TYPE_SHORT));
}
$mode = strtolower($this->req->getMeta('HTTP_X_AJAX', 'content'));
/** @var $stat WBStatistic_View */
$stat = WBClass::create('WBStatistic_View');
$path = implode('/', $this->path);
switch ($mode) {
case 'wxmldialog':
$ns = WBStatistic_View::NS_AJAX_WXMLDIALOG;
$result = $this->runWxmlDialog($status, $data, $checksum);
break;
case 'call':
$ns = WBStatistic_View::NS_AJAX_CALL;
$result = $this->runCall($status, $data, $checksum);
break;
case 'content':
default:
$ns = WBStatistic_View::NS_AJAX_CONTENT;
$result = $this->runContent($status, $data, $checksum);
break;
}
$stat->addPath($path, $ns);
return $result;
}
/**
* Wxml Dialog
*
* Display dialog of Wxml HTML editor
*
* @param int $status
* @param mixed $data to be send
* @param string $checksum
* @return bool true
*/
protected function runWxmlDialog(&$status, &$data, &$checksum = null)
{
if (empty($this->path)) {
WBClass::load('WBException_Argument');
throw WBException_Argument('Dialog name required but request path is empty', 1, __CLASS__);
}
$this->part = strtolower(array_shift($this->path));
if (empty($this->path)) {
$this->path = array('start');
}
$action = strtolower(array_shift($this->path));
/** @var WBWxml_Dialog */
$dialog = WBClass::create('WBWxml_Dialog_' . ucfirst($this->part));
switch ($action) {
case 'start':
$select = $this->req->get('sel', '');
$this->sess->set(__CLASS__ . ':select', $select);
$dialog->setSelect($select);
$dialog->setValue($this->req->get('val', ''));
$dialog->setAttributes($this->req->get('att', array()));
$dialog->start();
break;
case 'cancel':
break;
case 'submit':
default:
$select = $this->sess->get(__CLASS__ . ':select');
$dialog->setSelect($select);
$dialog->submit();
break;
}
$data = $dialog->getHtml();
$data .= $this->collectJavascript();
$status = 200;
return true;
}
/**
* AJAX remote function call
*
* @param int $status
* @param mixed $data to be send
* @param string $checksum
* @return bool true
*/
protected function runCall(&$status, &$data, &$checksum = null)
{
if (count($this->path) < 2) {
$status = 400;
$data = 'Invalid path to call AJAX method.';
return true;
}
$clazz = 'WBAjax_' . ucfirst(array_shift($this->path));
$method = array_shift($this->path);
$log = array(
'service' => 'ajaxcall',
'action' => 'error',
'msg' => '',
'class' => $clazz,
'method' => $method
);
try {
/** @var WBAjax */
$obj = WBClass::create($clazz);
$data = $obj->call($method);
$data .= $this->collectJavascript();
$this->addEventHeader();
$this->res->addHeader('X-AJAX-Method', $method);
} catch (WBException_Class $e) {
$status = 405;
$data = 'Invalid AJAX call';
$log['msg'] = $e->getMessage();
$log['code'] = $e->getCode();
$this->log->err($log);
return true;
} catch (Exception $e) {
$status = 500;
$data = 'Failed to process request';
$log['msg'] = $e->getMessage();
$log['code'] = $e->getCode();
$this->log->err($log);
return true;
}
return true;
}
/**
* Collect JavaScript
*
* Auxilliary function to fetch JavaScript scheduled for delivery.
* Hence, this is just a shortcut that uses WBHtml
*
* @uses WBHtml
* @return string
*/
private function collectJavaScript()
{
$js = array();
$js[] = WBHtml::get(WBHtml::AREA_HEAD);
$js[] = WBHtml::get(WBHtml::AREA_FOOT);
$onload = WBHtml::get(WBHtml::AREA_JS_ONLOAD);
if (!empty($onload)) {
$js[] = '';
}
return implode("\n", $js);
}
/**
* Replace content
*
* Use current ptocesser to replace content
*
* @param int $status
* @param mixed $data to be send
* @param string $checksum
* @return bool true
*/
protected function runContent(&$status, &$data, &$checksum = null)
{
// try to find config parameters in session
$paramName = md5($this->part . ':params:' . WBParam::get('wb/dir/base'));
if ($this->sess->has($paramName)) {
$sessConfig = $this->sess->get($paramName);
} else {
$sessConfig = $this->conf->get('content');
}
// load config from path - if given
$pagePath = trim($this->req->getMeta('HTTP_X_AJAX_PATH', ''), '/');
if (empty($pagePath)) {
$pagePath = array();
}
else {
$pagePath = explode('/', $pagePath);
}
if (!empty($pagePath)) {
$this->findLangInPath($pagePath);
$this->processPath($sessConfig, $pagePath);
$this->processPathRequest($sessConfig, $pagePath);
}
$this->req->path = implode('/', $pagePath);
// dig down to find actual
$config = &$sessConfig['content'];
if (isset($config[$this->path[0]])) {
$config = &$config[$this->path[0]];
for ($i = 1; $i < count($this->path); ++$i) {
$config = &$config['params']['content'][$this->path[$i]];
}
}
// invalid path!
if (!isset($config['processor']) || empty($config['processor'])) {
$status = 500;
$data = 'Invalid path! Maybe this was a mistake (or you were looking for vulnerable parts).';
$log = array(
'service' => 'ajaxcontent',
'action' => 'error',
'error' => 'invalid-path',
'part' => strval($this->part),
'path' => implode('/', $this->path)
);
$this->log->err($log);
return true;
}
// override parameters
if (isset($config['requestparams']) && is_array($config['requestparams'])){
if (empty($config['requestparams']) || !is_array($config['requestparams'])) {
$config['requestparams'] = array();
}
foreach ($config['requestparams'] as $name => $get) {
// there is no parameter name mapping
if (is_numeric($name)) {
$name = $get;
}
// set default
if (!isset($config['params'][$name])) {
$config['params'][$name] = null;
}
// copy parameter from requeist parameter
$config['params'][$name] = $this->req->get($get, $config['params'][$name]);
}
}
$log = array(
'service' => 'ajaxcontent',
'action' => 'run',
'error' => 'none',
'part' => strval($this->part),
'path' => implode('/', $this->path)
);
$log = array_merge($log, $config);
$this->log->notice($log);
if (empty($config['bubbles'])) {
$config['bubbles'] = array();
}
// run content component
$this->cont = WBClass::create('WBContent_' . $config['processor']);
$this->cont->configure(implode('/', $this->path), $config['params'], $config['bubbles']);
$config['params'] = $this->cont->run();
// get string from content, afterwards add JavaScript and other HTML gadgets
$data = $this->cont->getString();
$bubbles = $this->cont->getBubbles();
if (!empty($bubbles['title'])) {
WBHtml_JS::add(sprintf('WB.setTitle("%s")', $bubbles['title']), WBHtml::AREA_FOOT);
}
// force dynamic JavaScript Loader
WBHtml_JS::getIncluded(true);
// build complete data
$data = WBHtml::get(WBHtml::AREA_HEAD)
. $data
. WBHtml::get(WBHtml::AREA_FOOT);
// append onload javascript
$onload = WBHtml::get(WBHtml::AREA_JS_ONLOAD);
if (!empty($onload)) {
$data .= '';
}
// HTTP status
$status = $this->cont->getStatusCode();
$this->addEventHeader();
$this->sess->set($paramName, $sessConfig);
return true;
}
/**
* inform client about events
*
* Tell client that event has happend and what to do now
*
*/
private function addEventHeader()
{
// inform client about events
$q = $this->getEventQueue();
if (empty($q)) {
return;
}
$allowed = $this->conf->get('events');
foreach ($q as $e) {
if (!isset($allowed[$e['name']])) {
continue;
}
$action = $allowed[$e['name']];
if (isset($action['override'])) {
$this->res->addHeader('X-Site-Event', 'reload');
return;
}
if (isset($action['forward'])) {
$path = $action['forward'];
if ($this->useI18n) {
$reqPath = $this->confLocale->get('locale/languages/requestpath', array());
$lang = patI18n::getLocale(patI18n::LOCALE_TYPE_LANG);
if (isset($reqPath[$lang]) && '__default' != $reqPath[$lang]) {
$path = $reqPath[$lang] . '/' . $path;
}
}
$this->res->addHeader('X-Site-Event', 'redirect');
$this->res->addHeader('X-Site-Location', $path);
}
}
}
}