Controller.php 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. <?php
  2. /** @package verysimple::Phreeze */
  3. /** import supporting libraries */
  4. require_once("verysimple/HTTP/RequestUtil.php");
  5. require_once("verysimple/HTTP/Context.php");
  6. require_once("Phreezer.php");
  7. require_once("Criteria.php");
  8. require_once("IRouter.php");
  9. require_once("GenericRouter.php");
  10. require_once("verysimple/Authentication/IAuthenticatable.php");
  11. /**
  12. * Controller is a base controller object used for an MVC pattern
  13. * This controller uses Phreeze ORM and RenderEngine Template Engine
  14. * This controller could be extended to use a differente ORM and
  15. * Rendering engine as long as they implement compatible functions.
  16. *
  17. * @package verysimple::Phreeze
  18. * @author VerySimple Inc.
  19. * @copyright 1997-2011 VerySimple, Inc.
  20. * @license http://www.gnu.org/licenses/lgpl.html LGPL
  21. * @version 3.1
  22. */
  23. abstract class Controller
  24. {
  25. protected $Phreezer;
  26. protected $RenderEngine;
  27. /**
  28. * @var string ModelName is used by the base Controller class for certain functions in which
  29. * require knowledge of what Model is being used. For example, when validating user input.
  30. * This may be defined in Init() if any of thes base Controller features will be used.
  31. */
  32. protected $ModelName;
  33. protected $Context;
  34. /** @deprecated use RenderEngine */
  35. protected $Smarty;
  36. private $_router;
  37. private $_cu;
  38. public $GUID;
  39. public $DebugOutput = "";
  40. public $UnitTestMode = false;
  41. public $CaptureOutputMode = false;
  42. private $_terminate = false;
  43. /** Prefix this to the name of the view templates */
  44. static $SmartyViewPrefix = "View";
  45. /** search string to look for to determine if this is an API request or not */
  46. static $ApiIdentifier = "api/";
  47. /** the default mode used when calling 'Redirect' */
  48. static $DefaultRedirectMode = "client";
  49. /**
  50. * Constructor initializes the controller. This method cannot be overriden. If you need
  51. * to do something during construction, add it to Init
  52. *
  53. * @param Phreezer $phreezer Object persistance engine
  54. * @param IRenderEngine $renderEngine rendering engine
  55. * @param Context (optional) a context object for persisting the state of the current page
  56. * @param Router (optional) a custom writer for URL formatting
  57. */
  58. final function __construct(Phreezer $phreezer, $renderEngine, $context = null, IRouter $router = null)
  59. {
  60. $this->Phreezer =& $phreezer;
  61. $this->RenderEngine =& $renderEngine;
  62. // for backwards compatibility
  63. $this->Smarty =& $renderEngine;
  64. $ra = RequestUtil::GetRemoteHost();
  65. $this->GUID = $this->Phreezer->DataAdapter->GetDBName() . "_" . str_replace(".","_", $ra);
  66. $this->_router = $router ? $router : new GenericRouter();
  67. if ($context)
  68. {
  69. $this->Context =& $context;
  70. }
  71. else
  72. {
  73. $this->Context = new Context();
  74. $this->Context->GUID = "CTX_" . $this->GUID;
  75. }
  76. if ($this->RenderEngine)
  77. {
  78. // assign some variables globally for the views
  79. $this->Assign("CURRENT_USER",$this->GetCurrentUser());
  80. $this->Assign("URL",$this->GetRouter());
  81. $this->Assign("BROWSER_DEVICE",$this->GetDevice());
  82. // if feedback was persisted, set it
  83. $feedback = $this->Context->Get("feedback");
  84. // print_r($feedback); die('feedback');
  85. if (is_array($feedback)) {
  86. foreach ($feedback as $key => $val) {
  87. $this->Assign($key,$val);
  88. }
  89. }
  90. else {
  91. $this->Assign("feedback",$feedback);
  92. }
  93. $this->Context->Set("feedback",null);
  94. }
  95. $this->Init();
  96. }
  97. /**
  98. * Calling Terminate in Init will tell the dispatcher to halt execution
  99. * without calling the requested method/route
  100. */
  101. protected function Terminate()
  102. {
  103. $this->_terminate = true;
  104. }
  105. /**
  106. * Returns true if Terminate() has been fired
  107. * @return bool
  108. */
  109. public function IsTerminated()
  110. {
  111. return $this->_terminate;
  112. }
  113. /**
  114. * Returns the router object used to convert url/uri to controller method
  115. * @return IRouter
  116. */
  117. protected function GetRouter()
  118. {
  119. return $this->_router;
  120. }
  121. /**
  122. * Init is called by the base constructor immediately after construction.
  123. * This method must be implemented and provided an oportunity to
  124. * set any class-wide variables such as ModelName, implement
  125. * authentication for this Controller or any other class-wide initialization
  126. */
  127. abstract protected function Init();
  128. /**
  129. * Requires 401 Authentication. If authentication fails, this function
  130. * terminates with a 401 header. If success, sets CurrentUser and returns null.
  131. * @param IAuthenticatable any IAuthenticatable object
  132. * @param string http realm (basically the login message shown to the user)
  133. * @param string username querystring field (optional) if provided, the username can be passed via querystring instead of through the auth headers
  134. * @param string password querystring field (optional) if provided, the password can be passed via querystring instead of through the auth headers
  135. */
  136. protected function Require401Authentication(IAuthenticatable $authenticatable, $realm = "Login Required", $qs_username_field = "", $qs_password_field = "")
  137. {
  138. require_once("verysimple/Authentication/Auth401.php");
  139. $user = $this->Get401Authentication($authenticatable,$qs_username_field, $qs_password_field);
  140. // we only want to output 401 headers if the user is not already authenticated
  141. if (!$user)
  142. {
  143. if( $this->Get401AuthUsername($qs_username_field) )
  144. {
  145. // a username was provided, which means login failed
  146. Auth401::OutputHeaders("Invalid Login");
  147. }
  148. else
  149. {
  150. // no username provided, which means prompt for username
  151. Auth401::OutputHeaders($realm);
  152. }
  153. }
  154. }
  155. /**
  156. * Assign the current CSRFToken to the view layer
  157. * @param string $varname the view varname to use for assignment
  158. */
  159. protected function AssignCSRFToken($varname = 'CSRFToken')
  160. {
  161. $this->Assign($varname, $this->GetCSRFToken());
  162. }
  163. /**
  164. * Returns a stored CSRF Token from the session. If no token exists, then generate
  165. * one and save it to the session.
  166. *
  167. * @return string
  168. */
  169. protected function GetCSRFToken()
  170. {
  171. $token = $this->Context->Get('X-CSRFToken');
  172. if (!$token)
  173. {
  174. $token = md5(rand(1111111111,9999999999).microtime());
  175. $this->Context->Set('X-CSRFToken',$token);
  176. }
  177. return $token;
  178. }
  179. /**
  180. * Verify that X-CSRFToken was sent in the request headers and matches the session token
  181. * If not an Exception with be thrown. If no exception is thrown then the token
  182. * is verified.
  183. * @param string the name of the header variable that contains the token
  184. * @throws Exception if token is not provided or does not match
  185. */
  186. protected function VerifyCSRFToken($headerName = 'X-CSRFToken')
  187. {
  188. // check that a CSRF token is present in the request
  189. $headers = RequestUtil::GetHeaders();
  190. // make this case-insensitive (IE changes all headers to lower-case)
  191. $headers = array_change_key_case($headers, CASE_LOWER);
  192. $headerName = strtolower($headerName);
  193. if (array_key_exists($headerName, $headers))
  194. {
  195. if ($this->GetCSRFToken() != $headers[$headerName])
  196. {
  197. throw new Exception('Invalid CSRFToken');
  198. }
  199. }
  200. else
  201. {
  202. throw new Exception('Missing CSRFToken');
  203. }
  204. }
  205. /**
  206. * Start observing messages from Phreeze. If no observer is provided, then
  207. * an ObserveToBrowser will be used and debug messages will be output to the browser
  208. * @param IObserver $observer
  209. * @param bool $with_styles if true then basic styles will be output to the browser
  210. */
  211. protected function StartObserving($observer = null, $with_styles = true)
  212. {
  213. if ($observer == null)
  214. {
  215. require_once "ObserveToBrowser.php";
  216. $observer = new ObserveToBrowser();
  217. }
  218. if ($with_styles)
  219. {
  220. $this->PrintOut("<style>.debug, .query, .info {font-family: courier new; border-bottom: solid 1px #999;} .debug {color: blue;} .query {color: green;}</style>");
  221. }
  222. $this->Phreezer->AttachObserver($observer);
  223. }
  224. /**
  225. * accept username passed in either headers or querystring. if a querystring parameter name is
  226. * provided, that will be checked first before the 401 auth headers
  227. * @param string $qs_username_field the querystring parameter to check for username (optional)
  228. */
  229. protected function Get401AuthUsername($qs_username_field = "")
  230. {
  231. $qsv = $qs_username_field ? RequestUtil::Get($qs_username_field) : '';
  232. return $qsv ? $qsv : Auth401::GetUsername();
  233. }
  234. /**
  235. * accept password passed in either headers or querystring. if a querystring parameter name is
  236. * provided, that will be checked first before the 401 auth headers
  237. * @param string $qs_password_field the querystring parameter to check for password (optional)
  238. */
  239. protected function Get401AuthPassword($qs_password_field = "")
  240. {
  241. $qsv = $qs_password_field ? RequestUtil::Get($qs_password_field) : '';
  242. return $qsv ? $qsv : Auth401::GetPassword();
  243. }
  244. /**
  245. * Gets the user from 401 auth headers (or optionally querystring). There are three scenarios
  246. * - The user is already logged authenticated = IAuthenticatable is returned
  247. * - The user was not logged in and valid login credentials were provided = SetCurrentUser is called and IAuthenticatable is returned
  248. * - The user was not logged in and invalid (or no) credentials were provided = NULL is returned
  249. * @param IAuthenticatable any IAuthenticatable object
  250. * @param string username querystring field (optional) if provided, the username can be passed via querystring instead of through the auth headers
  251. * @param string password querystring field (optional) if provided, the password can be passed via querystring instead of through the auth headers
  252. * @return IAuthenticatable or NULL
  253. */
  254. protected function Get401Authentication(IAuthenticatable $authenticatable, $qs_username_field = "", $qs_password_field = "")
  255. {
  256. $user = null;
  257. $username = $this->Get401AuthUsername($qs_username_field);
  258. if( $username )
  259. {
  260. // username was provided so let's attempt a login
  261. $password = $this->Get401AuthPassword($qs_password_field);
  262. if ( $authenticatable->Login($username,$password) )
  263. {
  264. $user = $authenticatable;
  265. $this->SetCurrentUser($authenticatable);
  266. }
  267. }
  268. else
  269. {
  270. // no login info was provided so return whatever is in the session
  271. // (which will be null if the user is not authenticated)
  272. $user = $this->GetCurrentUser();
  273. }
  274. return $user;
  275. }
  276. /**
  277. * LoadFromForm should load the object specified by primary key = $pk, or
  278. * create a new instance of the object. Then should overwrite any applicable
  279. * properties with user input.
  280. *
  281. * This method is used by ValidateInput for automation AJAX server-side validation.
  282. *
  283. * @param variant $pk the primary key (optional)
  284. * @return Phreezable a phreezable object
  285. */
  286. protected function LoadFromForm($pk = null)
  287. {
  288. return null;
  289. }
  290. /**
  291. * Use as an alterative to print in order to capture debug output
  292. * @param string text to print
  293. * @param mime content type (example text/plain)
  294. */
  295. protected function PrintOut($text,$contentType = null)
  296. {
  297. if ($this->CaptureOutputMode)
  298. {
  299. $this->DebugOutput .= $text;
  300. }
  301. else
  302. {
  303. if ($contentType) header("Content-type: " . $contentType);
  304. print $text;
  305. }
  306. }
  307. /**
  308. * Returns a BrowserDevice object with information about the browser
  309. * that is being used to view/execute this code.
  310. * @return BrowserDevice
  311. */
  312. public function GetDevice()
  313. {
  314. require_once("verysimple/HTTP/BrowserDevice.php");
  315. return BrowserDevice::GetInstance();
  316. }
  317. /**
  318. * Displays the ListAll view for the primary model object. Because the
  319. * datagrid is populated via ajax, no model data is populated here
  320. */
  321. public function ListAll()
  322. {
  323. if (!$this->ModelName)
  324. {
  325. throw new Exception("ModelName must be defined in " . get_class($this) . "::ListAll");
  326. }
  327. // capture output instead of rendering if specified
  328. if ($this->CaptureOutputMode)
  329. {
  330. $this->DebugOutput = $this->RenderEngine->fetch("View" . $this->ModelName . "ListAll.tpl");
  331. }
  332. else
  333. {
  334. $this->RenderEngine->display("View" . $this->ModelName . "ListAll.tpl");
  335. }
  336. //$this->_ListAll(null, Request::Get("page",1), Request::Get("limit",20));
  337. }
  338. /**
  339. * Displays the ListAll view for the primary model object in the event that
  340. * ajax will not be used. The model data is populated
  341. *
  342. * @param Criteria $criteria
  343. * @param int $current_page number of the current page (for pagination)
  344. * @param int $limit size of the page (for pagination)
  345. */
  346. protected function _ListAll(Criteria $criteria, $current_page, $limit)
  347. {
  348. if (!$this->ModelName)
  349. {
  350. throw new Exception("ModelName must be defined in " . get_class($this) . "::_ListAll.");
  351. }
  352. $page = $this->Phreezer->Query($this->ModelName,$criteria)->GetDataPage($current_page,$limit);
  353. $this->RenderEngine->assign($this->ModelName . "DataPage", $page);
  354. $this->RenderEngine->display("View" . $this->ModelName . "ListAll.tpl");
  355. }
  356. /**
  357. * Renders a datapage as XML for use with a datagrid. The optional $additionalProps allows
  358. * retrieval of properties from foreign relationships
  359. *
  360. * @param DataPage $page
  361. * @param Array $additionalProps (In the format Array("GetObjName1"=>"PropName","GetObjName2"=>"PropName1,PropName2")
  362. * @param Array $supressProps (In the format Array("PropName1","PropName2")
  363. * @param bool noMap set to true to render this DataPage regardless of whether there is a FieldMap
  364. */
  365. protected function RenderXML($page,$additionalProps = null, $supressProps = null, $noMap = false)
  366. {
  367. require_once("verysimple/String/VerySimpleStringUtil.php");
  368. if (!is_array($supressProps)) $supressProps = array();
  369. // never include these props
  370. $suppressProps[] = "NoCache";
  371. $suppressProps[] = "CacheLevel";
  372. $suppressProps[] = "IsLoaded";
  373. $suppressProps[] = "IsPartiallyLoaded";
  374. $xml = "";
  375. $xml .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
  376. $xml .= "<DataPage>\r\n";
  377. $xml .= "<ObjectName>".htmlspecialchars($page->ObjectName)."</ObjectName>\r\n";
  378. $xml .= "<ObjectKey>".htmlspecialchars($page->ObjectKey)."</ObjectKey>\r\n";
  379. $xml .= "<TotalRecords>".htmlspecialchars($page->TotalResults)."</TotalRecords>\r\n";
  380. $xml .= "<TotalPages>".htmlspecialchars($page->TotalPages)."</TotalPages>\r\n";
  381. $xml .= "<CurrentPage>".htmlspecialchars($page->CurrentPage)."</CurrentPage>\r\n";
  382. $xml .= "<PageSize>".htmlspecialchars($page->PageSize)."</PageSize>\r\n";
  383. $xml .= "<Records>\r\n";
  384. // get the fieldmap for this object type unless not specified
  385. if ($noMap)
  386. {
  387. $fms = array();
  388. }
  389. else
  390. {
  391. try
  392. {
  393. $fms = $this->Phreezer->GetFieldMaps($page->ObjectName);
  394. }
  395. catch (exception $ex)
  396. {
  397. throw new Exception("The objects contained in this DataPage do not have a FieldMap. Set noMap argument to true to supress this error: " . $ex->getMessage());
  398. }
  399. }
  400. foreach ($page->Rows as $obj)
  401. {
  402. $xml .= "<" . htmlspecialchars($page->ObjectName) . ">\r\n";
  403. foreach (get_object_vars($obj) as $var => $val)
  404. {
  405. if (!in_array($var,$supressProps))
  406. {
  407. // depending on what type of field this is, do some special formatting
  408. $fm = isset($fms[$var]) ? $fms[$var]->FieldType : FM_TYPE_UNKNOWN;
  409. if ($fm == FM_TYPE_DATETIME)
  410. {
  411. $val = strtotime($val) ? date("m/d/Y h:i A",strtotime($val)) : $val;
  412. }
  413. elseif ($fm == FM_TYPE_DATE)
  414. {
  415. $val = strtotime($val) ? date("m/d/Y",strtotime($val)) : $val;
  416. }
  417. // if the developer has added a property that is not a simple type
  418. // we need to serialize it
  419. if (is_array($val) || is_object($val))
  420. {
  421. $val = serialize($val);
  422. }
  423. $val = VerySimpleStringUtil::EncodeSpecialCharacters($val, true, true);
  424. $xml .= "<" . htmlspecialchars($var) . ">" . $val . "</" . htmlspecialchars($var) . ">\r\n";
  425. }
  426. }
  427. // Add any properties that we want from child objects
  428. if ($additionalProps)
  429. {
  430. foreach ($additionalProps as $meth => $propPair)
  431. {
  432. $props = explode(",",$propPair);
  433. foreach ($props as $prop)
  434. {
  435. $xml .= "<" . htmlspecialchars($meth . $prop) . ">" . htmlspecialchars($obj->$meth()->$prop) . "</" . htmlspecialchars($meth . $prop) . ">\r\n";
  436. }
  437. }
  438. }
  439. $xml .= "</" . htmlspecialchars($page->ObjectName) . ">\r\n";
  440. }
  441. $xml .= "</Records>\r\n";
  442. $xml .= "</DataPage>\r\n";
  443. // capture output instead of rendering if specified
  444. if ($this->CaptureOutputMode)
  445. {
  446. $this->DebugOutput = $xml;
  447. }
  448. else
  449. {
  450. header('Content-type: text/xml');
  451. print $xml;
  452. }
  453. }
  454. /**
  455. * Render an array of IRSSFeedItem objects as an RSS feed
  456. * @param array $feedItems array of IRSSFeedItem objects
  457. * @param string $feedTitle
  458. * @param string $feedDescription
  459. */
  460. protected function RenderRSS(Array $feedItems, $feedTitle = "RSS Feed", $feedDescription = "RSS Feed")
  461. {
  462. require_once('verysimple/RSS/Writer.php');
  463. require_once('verysimple/RSS/IRSSFeedItem.php');
  464. $baseUrl = RequestUtil::GetBaseURL();
  465. $rssWriter = new RSS_Writer($feedTitle,$baseUrl,$feedDescription);
  466. $rssWriter->setLanguage('us-en');
  467. $rssWriter->addCategory("Items");
  468. if (count($feedItems))
  469. {
  470. $count = 0;
  471. foreach ($feedItems as $item)
  472. {
  473. $count++;
  474. if ($item instanceof IRSSFeedItem)
  475. {
  476. $rssWriter->addItem(
  477. $item->GetRSSTitle(), // title
  478. $item->GetRSSLink($baseUrl), // link
  479. $item->GetRSSDescription(), // description
  480. $item->GetRSSAuthor(), // author
  481. date(DATE_RSS, $item->GetRSSPublishDate()), // date
  482. null, // source
  483. $item->GetRSSGUID() // guid
  484. );
  485. }
  486. else
  487. {
  488. $rssWriter->addItem("Item $count doesn't implment IRSSFeedItem","about:blank",'','Error',date(DATE_RSS) );
  489. }
  490. }
  491. }
  492. else
  493. {
  494. $rssWriter->addItem("No Items","about:blank",'','No Author',date(DATE_RSS) );
  495. }
  496. $rssWriter->writeOut();
  497. }
  498. /**
  499. * @deprecated use Controller->Context->Set instead
  500. */
  501. protected function Set($var,$val)
  502. {
  503. return $this->Context->Set($var,$val);
  504. }
  505. /**
  506. * @deprecated use Controller->Context->Get instead
  507. */
  508. protected function Get($var,$default=null)
  509. {
  510. return $this->Context->Get($var,$default);
  511. }
  512. /**
  513. * This method calls LoadFromForm to retrieve a model object populated with user
  514. * input. The input is validated and a ValidationResponse is rendered in JSON format
  515. *
  516. * if Request::Get("SaveInline") is set then validate will call Save instead of
  517. * rendering JSON. In which case, your Save method should render the ValidationResponse
  518. */
  519. function ValidateInput()
  520. {
  521. require_once("ValidationResponse.php");
  522. $vr = new ValidationResponse();
  523. $save = RequestUtil::Get("SaveInline");
  524. $obj = $this->LoadFromForm();
  525. if (!is_object($obj))
  526. {
  527. $vr->Success = false;
  528. $vr->Errors = array("Unknown"=>"LoadFromForm does not appear to be implemented. Unable to validate");
  529. $vr->Message = "LoadFromForm does not appear to be implemented. Unable to validate";
  530. }
  531. elseif ($obj->Validate())
  532. {
  533. $vr->Success = true;
  534. }
  535. else
  536. {
  537. $vr->Success = false;
  538. $vr->Errors = $obj->GetValidationErrors();
  539. $vr->Message = "Validation Errors Occured";
  540. }
  541. // if the user requested to save inline, their Save method will take over from here
  542. if ($vr->Success && $save)
  543. {
  544. $this->Save();
  545. }
  546. else
  547. {
  548. $this->RenderJSON($vr);
  549. }
  550. }
  551. /**
  552. * Stub method
  553. */
  554. function Save()
  555. {
  556. if ( !RequestUtil::Get("SaveInline") )
  557. {
  558. throw new Exception("Save is not implemented by this controller");
  559. }
  560. require_once("ValidationResponse.php");
  561. $vr = new ValidationResponse();
  562. $vr->Success = false;
  563. $vr->Errors = array();
  564. $vr->Message = "SaveInline is not implemented by this controller";
  565. $this->RenderJSON($vr);
  566. }
  567. /**
  568. * Returns an array of all property names in the primary model
  569. *
  570. * @return array
  571. */
  572. protected function GetColumns()
  573. {
  574. if (!$this->ModelName)
  575. {
  576. throw new Exception("ModelName must be defined in " . get_class($this) . "::GetColumns");
  577. }
  578. $counter = 0;
  579. $props = array();
  580. foreach (get_class_vars($this->ModelName)as $var => $val)
  581. {
  582. $props[$counter++] = $var;
  583. }
  584. return $props;
  585. }
  586. /**
  587. * Returns a unique ID for this session based on connection string and remote IP
  588. * This is a reasonable variable to use as a session variable because it ensures
  589. * that if other applications on the same server are running phreeze, there won't
  590. * be cross-application authentication issues. Additionally, the remote ip
  591. * helps to make session hijacking more difficult
  592. *
  593. * @deprecated use $controller->GUID instead
  594. * @return string
  595. */
  596. private function GetGUID()
  597. {
  598. return $this->GUID;
  599. }
  600. /**
  601. * Clears the current authenticated user from the session
  602. */
  603. public function ClearCurrentUser()
  604. {
  605. require_once("verysimple/Authentication/Authenticator.php");
  606. $this->_cu = null;
  607. Authenticator::ClearAuthentication($this->GUID);
  608. }
  609. /**
  610. * Sets the given user as the authenticatable user for this session.
  611. *
  612. * @param IAuthenticatable The user object that has authenticated
  613. */
  614. protected function SetCurrentUser(IAuthenticatable $user)
  615. {
  616. $this->_cu = $user;
  617. Authenticator::SetCurrentUser($user,$this->GUID);
  618. // assign some global variables to the view
  619. $this->Assign("CURRENT_USER",$this->GetCurrentUser());
  620. }
  621. /**
  622. * Returns the currently authenticated user, or null if a user has not been authenticated.
  623. *
  624. * @return IAuthenticatable || null
  625. */
  626. protected function GetCurrentUser()
  627. {
  628. if (!$this->_cu)
  629. {
  630. require_once("verysimple/Authentication/Authenticator.php");
  631. $this->Phreezer->Observe("Loading CurrentUser from Session");
  632. $this->_cu = Authenticator::GetCurrentUser($this->GUID);
  633. if ($this->_cu )
  634. {
  635. if (get_class($this->_cu) == "__PHP_Incomplete_Class")
  636. {
  637. // this happens if the class used for authentication was not included before the session was started
  638. $tmp = print_r($this->_cu,1);
  639. $parts1 = explode("__PHP_Incomplete_Class_Name] => ",$tmp);
  640. $parts2 = explode("[",$parts1[1]);
  641. $name = trim($parts2[0]);
  642. Authenticator::ClearAuthentication($this->GUID);
  643. throw new Exception("The class definition used for authentication '$name' must be defined (included) before the session is started, for example in _app_config.php.");
  644. }
  645. else
  646. {
  647. // refresh the current user if the object supports it
  648. if (method_exists($this->_cu, 'Refresh')) $this->_cu->Refresh($this->Phreezer);
  649. }
  650. }
  651. }
  652. else
  653. {
  654. $this->Phreezer->Observe("Using previously loaded CurrentUser");
  655. }
  656. return $this->_cu;
  657. }
  658. /**
  659. * Returns true if this request is an API request. This examines the URL to
  660. * see if the string Controller::$ApiIdentifier is in the URL
  661. * @return bool
  662. */
  663. public function IsApiRequest()
  664. {
  665. $url = RequestUtil::GetCurrentURL();
  666. return (strpos($url, self::$ApiIdentifier ) !== false);
  667. }
  668. /**
  669. * Check the current user to see if they have the requested permission.
  670. * If so then the function does nothing. If not, then the user is redirected
  671. * to $on_fail_action (if provided) or an AuthenticationException is thrown.
  672. * if Controller->IsApiRequest() returns true then an AuthenticationException will
  673. * be thrown regardless of the fail_action.
  674. *
  675. * @param int $permission Permission ID requested
  676. * @param string $on_fail_action (optional) The action to redirect if require fails
  677. * @param string $not_authenticated_feedback (optional) Feedback to forward to the on_fail_action if user is not logged in
  678. * @param string $permission_denied_feedback (optional) Feedback to forward to the on_fail_action if user is logged in but does not have permission
  679. * @throws AuthenticationException
  680. */
  681. protected function RequirePermission($permission, $on_fail_action = "", $not_authenticated_feedback = "Please login to access this page", $permission_denied_feedback = "You are not authorized to view this page and/or your session has expired")
  682. {
  683. $this->Phreezer->Observe("Checking For Permission '$permission'");
  684. $cu = $this->GetCurrentUser();
  685. if (!$cu || !$cu->IsAuthorized($permission))
  686. {
  687. $message = !$cu || $cu->IsAnonymous()
  688. ? $not_authenticated_feedback
  689. : $permission_denied_feedback;
  690. if ($on_fail_action && $this->IsApiRequest() == false)
  691. {
  692. $this->Redirect($on_fail_action,array('feedback'=>$message,'warning'=>$message));
  693. }
  694. else
  695. {
  696. $ex = new AuthenticationException($message,500);
  697. $this->Crash("Permission Denied",500,$ex);
  698. }
  699. }
  700. }
  701. /**
  702. * Assigns a variable to the view
  703. *
  704. * @param string $varname
  705. * @param variant $varval
  706. */
  707. protected function Assign($varname,$varval)
  708. {
  709. $this->RenderEngine->assign($varname,$varval);
  710. }
  711. /**
  712. * Renders the specified view
  713. *
  714. * @param string $view (optional) if not provided, the view is automatically bound using the class and method name
  715. * @param string $format (optional) defaults to $self::SmartyViewPrefix
  716. */
  717. protected function Render($view="",$format = null)
  718. {
  719. $isSmarty = (strpos(get_class($this->RenderEngine),"Smarty") > -1);
  720. if ($isSmarty && $format == null) $format = self::$SmartyViewPrefix;
  721. if ($format == null) $format = '';
  722. if ($view == "")
  723. {
  724. // automatic binding
  725. $backtrace = debug_backtrace();
  726. $view = str_replace("Controller","", $backtrace[1]['class']) . $backtrace[1]['function'];
  727. }
  728. // if the render engine is Smarty then add the '.tpl' suffix
  729. $viewPath = $isSmarty ? $format.$view.".tpl" : $format.$view;
  730. // capture output instead of rendering if specified
  731. if ($this->CaptureOutputMode)
  732. {
  733. $this->DebugOutput = $this->RenderEngine->fetch($viewPath);
  734. }
  735. else
  736. {
  737. $this->RenderEngine->display($viewPath);
  738. }
  739. }
  740. /**
  741. * Renders the given value as JSON
  742. *
  743. * @param variant the variable, array, object, etc to be rendered as JSON
  744. * @param string if a callback is provided, this will be rendered as JSONP
  745. * @param bool if true then objects will be returned ->GetObject() (only supports ObjectArray or individual Phreezable or Reporter object)
  746. * @param array (only relvant if useSimpleObject is true) options array passed through to Phreezable->ToString()
  747. * @param bool set to 0 to leave data untouched. set to 1 to always force value to UTF8. set to 2 to only force UTF8 if an encoding error occurs (WARNING: options 1 or 2 will likely result in unreadable characters. The recommended fix is to set your database charset to utf8)
  748. */
  749. protected function RenderJSON($var, $callback = "",$useSimpleObject = false, $options = null, $forceUTF8 = 0)
  750. {
  751. $obj = null;
  752. if (is_a($var,'DataSet') || is_a($var,'DataPage'))
  753. {
  754. // if a dataset or datapage can be converted directly into an array without enumerating twice
  755. $obj = $var->ToObjectArray($useSimpleObject,$options);
  756. }
  757. else if ($useSimpleObject)
  758. {
  759. // we need to figure out what type
  760. if (is_array($var) || is_a($var,'SplFixedArray') )
  761. {
  762. $obj = array();
  763. foreach ($var as $item)
  764. {
  765. $obj[] = $item->ToObject($options);
  766. }
  767. }
  768. elseif (is_a($var,'Phreezable') || is_a($var,'Reporter'))
  769. {
  770. $obj = $var->ToObject($options);
  771. }
  772. else
  773. {
  774. throw new Exception('RenderJSON could not determine the type of object to render');
  775. }
  776. }
  777. else
  778. {
  779. $obj = $var;
  780. }
  781. if ($forceUTF8 == 1) $this->UTF8Encode($obj);
  782. try
  783. {
  784. $output = json_encode($obj);
  785. }
  786. catch (Exception $ex)
  787. {
  788. if (strpos($ex->getMessage(),'Invalid UTF-8') !== false)
  789. {
  790. // a UTF encoding problem has been encountered
  791. if ($forceUTF8 == 2)
  792. {
  793. $this->UTF8Encode($obj);
  794. $output = json_encode($obj);
  795. }
  796. else
  797. {
  798. throw new Exception('The object to be encoded contains invalid UTF-8 data. Please verify your database character encoding or alternatively set the Controller::RenderJSON $forceUTF8 parameter to 1 or 2.');
  799. }
  800. }
  801. else
  802. {
  803. // we don't know what this is so don't try to handle it here
  804. throw $ex;
  805. }
  806. }
  807. if ($callback) $output = "$callback(" . $output . ")";
  808. // capture output instead of rendering if specified
  809. if ($this->CaptureOutputMode)
  810. {
  811. $this->DebugOutput = $output;
  812. }
  813. else
  814. {
  815. @header($callback ? 'Content-type: text/plain' : 'Content-type: application/json');
  816. print $output;
  817. }
  818. }
  819. /**
  820. * Send a crash message to the browser and terminate
  821. *
  822. * @param string $errmsg text message to display
  823. * @param int $code used for support for this error
  824. * @param Exception $exception if exception was thrown, can be provided for more info
  825. */
  826. protected function Crash($errmsg = "Unknown Error", $code = 0, $exception = null)
  827. {
  828. $ex = $exception ? $exception : new Exception($errmsg, $code);
  829. throw $ex;
  830. }
  831. /**
  832. * Redirect to the appropriate page based on the action. This function will
  833. * call "exit" so do not put any code that you wish to execute after Redirect
  834. *
  835. * @param string $action in the format Controller.Method
  836. * @param mixed $feedback string which will be assigne to the template as "feedback" or an array of values to assign
  837. * @param array $params
  838. * @param string $mode (client | header) default = Controller::$DefaultRedirectMode
  839. */
  840. protected function Redirect($action, $feedback = null, $params = "", $mode = "")
  841. {
  842. if (!$mode) $mode = self::$DefaultRedirectMode;
  843. $params = is_array($params) ? $params : array();
  844. if ($feedback != null)
  845. {
  846. $this->Context->Set("feedback",$feedback);
  847. }
  848. // support for deprecated Controller/Method format
  849. list($controller,$method) = explode(".", str_replace("/",".",$action));
  850. $url = $this->GetRouter()->GetUrl($controller,$method,$params);
  851. // capture output instead of rendering if specified
  852. if ($this->CaptureOutputMode)
  853. {
  854. if ($mode == 'client')
  855. {
  856. $this->RenderEngine->assign("url",$url);
  857. $this->DebugOutput = $this->RenderEngine->fetch("_redirect.tpl");
  858. }
  859. else
  860. {
  861. $this->DebugOutput = 'Location: ' . $url;
  862. }
  863. }
  864. else
  865. {
  866. if ($mode == 'client')
  867. {
  868. $this->RenderEngine->assign("url",$url);
  869. $this->RenderEngine->display("_redirect.tpl");
  870. }
  871. else
  872. {
  873. header('Location: ' . $url) ;
  874. }
  875. }
  876. // don't exit if we are unit testing because it will stop all further tests
  877. if (!$this->UnitTestMode) exit;
  878. }
  879. /**
  880. * Does a recursive UTF8 encoding on a string/array/object. This is used
  881. * when json encoding fails due to non UTF8 in the database that cannot
  882. * be repaired with any other means.
  883. *
  884. * NOTE: this does not have a return value. value is passed by reference and updated
  885. *
  886. * @param variant $input
  887. */
  888. private function UTF8Encode(&$input)
  889. {
  890. if (is_string($input))
  891. {
  892. // pop recursion here
  893. $input = utf8_encode($input);
  894. }
  895. else if (is_array($input))
  896. {
  897. foreach ($input as &$value)
  898. {
  899. $this->UTF8Encode($value);
  900. }
  901. unset($value);
  902. }
  903. else if (is_object($input))
  904. {
  905. $vars = array_keys(get_object_vars($input));
  906. foreach ($vars as $var)
  907. {
  908. $this->UTF8Encode($input->$var);
  909. }
  910. }
  911. }
  912. /**
  913. * Throw an exception if an undeclared method is accessed
  914. *
  915. * @access public
  916. * @param string $name
  917. * @param variant $vars
  918. * @throws Exception
  919. */
  920. function __call($name,$vars = null)
  921. {
  922. throw new Exception(get_class($this) . "::" . $name . " is not implemented");
  923. }
  924. }
  925. ?>