* @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.3.1 * @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')); $stat = WBClass::create('WBStatistic_View'); /** @var $stat WBStatistic_View */ $path = implode('/', $this->path); switch ($mode) { case 'xinhadialog': $ns = WBStatistic_View::NS_AJAX_XINHADIALOG; $result = $this->runXinhaDialog($status, $data, $checksum); break; 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();; $status = 200; return true; } /** * Xinha Dialog * * Display dialog of Xinha HTML editor * * @param int $status * @param mixed $data to be send * @param string $checksum * @return bool true */ protected function runXinhaDialog(&$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)); $action = 'start'; if (!empty($this->path)) { $action = array_shift($this->path); } $dialog = WBClass::create('WBXinha_Dialog_' . ucfirst($this->part)); /** @var $dialog WBXinha_Dialog */ // fetch selected text $select = ''; $value = ''; $submit = true; if ($action == 'start') { $value = $this->req->get('value'); $select = $this->req->get('select'); $submit = false; $dialog->setAttributes($this->req->get('att')); } else if ($this->sess->has(__CLASS__ . ':select')) { $select = $this->sess->get(__CLASS__ . ':select'); } $dialog->setValue($value); $dialog->setSelect($select); // load and process dialog class $info = array( 'dialog' => $this->part, 'action' => $action, 'success' => '0', 'html' => '', 'select' => $select, 'value' => $value, 'form' => 'Please wait' ); if ($dialog->run($submit)) { $info['success'] = '1'; $info['html'] = $dialog->getInsertHtml(); } else { $info['form'] = $dialog->getHtml(); } $this->tmpl = WBClass::create('patTemplate'); $this->tmpl->readTemplatesFromInput('Xinha/dialog.tmpl'); $this->tmpl->addGlobalVars($info, 'DIALOG_'); // keep selected text in session $this->sess->set(__CLASS__ . ':select', $select); $data = $this->tmpl->getParsedTemplate('snippet'); $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 { $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); // run content component $this->cont = WBClass::create('WBContent_' . $config['processor']); $this->cont->configure(implode('/', $this->path), $config['params'], array()); $config['params'] = $this->cont->run(); // get string from content, afterwards add JavaScript and other HTML gadgets $data = $this->cont->getString(); // 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 = 200; $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])) { $path = $reqPath[$lang] . '/' . $path; } } $this->res->addHeader('X-Site-Event', 'redirect'); $this->res->addHeader('X-Site-Location', $path); } } } }