Edit via SFTP
  1. <?php
  2. /*
  3.   FILE: app.php
  4.   PURPOSE: generic/abstract application framework
  5.   NOTE: This was written with the assumption that we're writing a *web* application,
  6.   not desktop. If Ferreteria ever diversifies into desktop, these classes may need
  7.   further refinement.
  8.   HISTORY:
  9.   2012-05-08 split off from store.php
  10.   2013-10-23 stripped for use with ATC app (renamed as app.php)
  11.   2013-11-11 re-adapted for general library
  12.   2016-10-01 changes to work with db.v2
  13.   2017-03-25 moving user-security permission constants here
  14. */
  15.  
  16. define('KWP_FERRETERIA_DOC','http://wooz.dev/Ferreteria');
  17. define('KWP_FERRETERIA_DOC_ERRORS',KWP_FERRETERIA_DOC.'/errors');
  18.  
  19. // ++ EVENTS ++ //
  20.  
  21. define('KS_EVENT_SUCCESS','fe.OK');
  22. define('KS_EVENT_FAILED','fe.ERR');
  23. define('KS_EVENT_NO_CHANGE','fe.STET'); // data left unaltered
  24. define('KS_EVENT_NEW_RECORD','fe.NEW');
  25. define('KS_EVENT_CHANGE_RECORD','fe.CHG');
  26. define('KS_EVENT_FERRETERIA_SUSPICIOUS_INPUT','fe.suspicious');
  27. define('KS_EVENT_FERRETERIA_EMAIL_SENT','fe.email.sent');
  28. define('KS_EVENT_FERRETERIA_SENDING_ADMIN_EMAIL','fe.email.req');
  29.  
  30. use ferret\cEnv;
  31. use ferret\cValue;
  32. use ferret\cValueReadOnly;
  33. use ferret\data\cTableResult;
  34. use ferret\data\cRecordResult;
  35. use ferret\data\cIOResult;
  36.  
  37. /*::::
  38.   PURPOSE: application framework base class -- container for the application
  39.   This API assumes a single primary database, although it's possible to access another db explicitly.
  40.   See Greenmine:FinanceFerret for an example.
  41. */
  42. abstract class fcApp {
  43. use \ferret\globals\tGlobalDescendable;
  44. use ftVerbalObject;
  45.  
  46. // use SingletonTool constructor enforcement
  47. public function __construct() { $this->PreventDuplicates(); }
  48.  
  49. // ++ STATIC ++ //
  50.  
  51. static public function Globals() { return \ferret\globals\cAbstract::Me(); }
  52.  
  53. static private array $arSingles = [];
  54. static public function AddSingle(object $o) {
  55. $sClass = get_class($o);
  56. if (array_key_exists($sClass,self::$arSingles)) {
  57. $sError = "attempted to register duplicate instance of $sClass.";
  58. $e = new cUsage($sError);
  59. $htOldID = cEnv::BoldIt(spl_object_id(self::$arSingles[$sClass]));
  60. $htNewID = cEnv::BoldIt(spl_object_id($this));
  61. $e->AddDiagnostic("(old ID $htOldID | new ID $htNewID)");
  62. $sList = cEnv::OpenList();
  63. foreach (self::$arSelves as $sClass => $o) {
  64. $sList .= cEnv::ListItem($sClass);
  65. }
  66. $sList .= cEnv::ShutList();
  67. $nItems = count(self::$arSelves);
  68. $sPlur = \ferret\csString::Pluralize($nItems);
  69. $htList = cEnv::HideDrop("$nItems object$sPlur created:",$sList);
  70. $e->AddDiagnostic($htList);
  71.  
  72. throw $e;
  73. }
  74. }
  75.  
  76. // -- STATIC -- //
  77. // ++ SETUP ++ //
  78.  
  79. private $wp = NULL;
  80. //SHORTCUT (2021-08-28 Who calls this, and why don't they just go directly?)
  81. protected function GetBaseWebPath() : string { return self::Globals()->GetWebPath_forAppBase(); }
  82.  
  83. // -- SETUP -- //
  84. // ++ ACTION ++ //
  85.  
  86. abstract public function Go();
  87. abstract public function AddContentString(string $s);
  88.  
  89. //++cookies++//
  90. /*
  91.   RULES:
  92.   * Cookie names set are prefixed with the application key (KS_APP_KEY).
  93.   * The local array only stores cookies to be set (modified).
  94.   * Only those are written (thrown) to the browser.
  95.   * For received cookie values, only those prefixed with KS_APP_KEY are returned.
  96.   (KS_APP_KEY is prepended to the keyname requested before it is looked up.)
  97.   HISTORY:
  98.   2018-04-24 written, originally to replace storage of stashed messages, but
  99.   on further thought I decided it's (maybe?) more efficient to keep saving them
  100.   on the server as I had been doing (though it's a pretty close call).
  101.   ...but it still provides a more generalized way of storing the session key,
  102.   so now I'm using it for that.
  103.  
  104.   ...and then on further further thought, I decided: well, having written this,
  105.   why not use it for now, rather than getting into the complexities of fixing
  106.   the on-disk storage method?
  107.   2018-04-28 removed ThrowCookie() and ThrowCookies()
  108.   SetRawCookieValue() now actually sets (throws) the cookie.
  109. */
  110.  
  111. private $arCookies=NULL; // local copy of $_COOKIE[], updated with any sent-cookie values
  112. // 2018-04-25 written
  113. protected function SetRawCookieValue(string $sKey,string $sVal) : bool {
  114. $wpBase = $this->GetBaseWebPath();
  115. try {
  116. $ok = setcookie($sKey,$sVal,0,$wpBase);
  117. } catch(Exception $e) {
  118. $ok = FALSE;
  119. }
  120. if ($ok) {
  121. $this->arCookies[$sKey] = $sVal;
  122. } else {
  123. // DEBUGGING (TODO: log when this happens)
  124. $htKey = cEnv::BoldIt($sKey);
  125. $htVal = cEnv::BoldIt($sVal);
  126. echo "Could not set cookie [$htKey] to value [$htVal].".cEnv::NewLine();
  127. $oTrace = new ferret\cStackTrace();
  128. echo $oTrace->RenderTrace();
  129. }
  130. return $ok;
  131. }
  132. /*----
  133.   THINKING: I *think* the way it works is that cookies are set from the array,
  134.   so only what's in the array will get set. There doesn't seem to be any
  135.   system call for removing a cookie.
  136.   HISTORY:
  137.   2020-12-12 written so we can enforce string as the cookie value
  138.   */
  139. public function ClearCookie(string $sKey) : void {
  140. unset($this->arCookies[$sKey]);
  141. }
  142. protected function GetRawCookie(string $sKey) : cValue {
  143. if (is_null($this->arCookies)) {
  144. $this->arCookies = $_COOKIE;
  145. }
  146. $bFound = array_key_exists($sKey,$this->arCookies);
  147. $v = $bFound ? ($this->arCookies[$sKey]) : NULL;
  148. return new cValueReadOnly($v,$bFound);
  149. }
  150. static protected function MakeRawCookieKey(string $sKey) : string {
  151. return self::Globals()->GetAppKeyString().'-'.$sKey;
  152. }
  153. // 2018-04-24 written
  154. public function SetCookieValue(string $sKey,string $sValue) {
  155. $sKeyFull = self::MakeRawCookieKey($sKey);
  156. return $this->SetRawCookieValue($sKeyFull,$sValue);
  157. }
  158. // 2018-04-24 written
  159. public function GetCookie(string $sKey) : cValue {
  160. $sKeyFull = self::MakeRawCookieKey($sKey);
  161. $osCookie = $this->GetRawCookie($sKeyFull);
  162. return $osCookie;
  163. }
  164.  
  165. //--cookies--//
  166.  
  167. // -- ACTION -- //
  168. // ++ CLASSES ++ //
  169.  
  170. abstract protected function GetPageClass() : string;
  171. abstract protected function GetKioskClass() : string;
  172. abstract protected function GetDropinManagerClass() : string;
  173.  
  174. // -- CLASSES -- //
  175. // ++ OBJECTS ++ //
  176.  
  177. private $oPage = NULL;
  178. public function GetPageObject() {
  179. if (is_null($this->oPage)) {
  180. $sClass = $this->GetPageClass();
  181. //echo "PAGE CLASS = $sClass<br>";
  182. $this->oPage = new $sClass();
  183. }
  184. return $this->oPage;
  185. }
  186. private $oKiosk=NULL;
  187. public function GetKioskObject() {
  188. if (is_null($this->oKiosk)) {
  189. $sClass = $this->GetKioskClass();
  190. $this->oKiosk = new $sClass($this->GetBaseWebPath());
  191. }
  192. return $this->oKiosk;
  193. }
  194. #protected function CreateKioskObject(string $sClass,string $wp) { $this->oKiosk = new $sClass($wp); }
  195. private $oHdrMenu = NULL;
  196. public function GetHeaderMenu() {
  197. if (is_null($this->oHdrMenu)) {
  198. $this->oHdrMenu = $this->GetPageObject()->GetElement_HeaderMenu();
  199. }
  200. return $this->oHdrMenu;
  201. }
  202. private $oDropinMgr = NULL;
  203. public function GetDropinManager() {
  204. if (is_null($this->oDropinMgr)) {
  205. $sTableClass = $this->GetDropinManagerClass();
  206. $sSpaceClass = cTableWorkSpace::class;
  207.  
  208. // make Table
  209. $t = new $sTableClass;
  210. $t->Space()->Database()->SetIt($this->GetDatabase());
  211.  
  212. /* 2021-08-29 old code
  213.   // not a sourced table
  214.   $db = $this->GetDatabase();
  215.   //return $db->MakeTableWrapper($sClass);
  216.   $t = new $sClass($db);
  217.   */
  218. $this->oDropinMgr = $t;
  219. }
  220. return $this->oDropinMgr;
  221. }
  222.  
  223. // -- OBJECTS -- //
  224. // ++ FIELD CALCULATIONS ++ //
  225.  
  226. /*----
  227.   NOTES:
  228.   2016-05-22 It seems like a good idea to have this, to pass to record-creation methods that ask for it.
  229.   2020-01-10 Maybe it should return a cElementResult and be called UserID_status()?
  230.   2021-03-26 That * was done awhile back. Just now changed cElementResult to cValueResult.
  231.   PUBLIC so cart.logic can use it
  232.   */
  233. public function StatusOfUserID() : ferret\cValueReadOnly {
  234. return $this->GetSessionStatus()->Record()->GetIt()->UserID();
  235. }
  236. /*----
  237.   RETURNS: User login string, or NULL if user not logged in
  238.   HISTORY:
  239.   2014-07-27 Written because this seems to be where it belongs.
  240.   May duplicate functionality in Page object. Why is that there?
  241.   */
  242. public function LoginName() : ?string {
  243. if ($this->UserIsLoggedIn()) {
  244. return $this->GetUserRecord()->LoginName();
  245. } else {
  246. return NULL;
  247. }
  248. }
  249.  
  250. // -- FIELD CALCULATIONS -- //
  251. }
  252. /*::::
  253.   PURPOSE: App which needs data storage
  254.   HISTORY:
  255.   2020-11-26 split off from fcApp
  256. */
  257. abstract class fcDataApp extends fcApp {
  258.  
  259. static protected function TableNamesClass() : string { return \ferret\globals\cTableNames::class; }
  260. static private $ogTableNames = NULL;
  261. static public function TableNames() : \ferret\globals\cTableNames {
  262. if (is_null(self::$ogTableNames)) {
  263. $sClass = static::TableNamesClass();
  264. self::$ogTableNames = new $sClass();
  265. }
  266. return self::$ogTableNames;
  267. }
  268.  
  269. abstract public function GetDatabase() : ferret\data\cDatabase;
  270.  
  271. }
  272. /*::::
  273.   PURPOSE: App which manages user logins
  274.   HISTORY:
  275.   2020-11-26 split off from fcApp
  276.   Some of this stuff could be abstracted into a user-management API,
  277.   e.g. for using LDAP or something, but for now I'm just implementing it.
  278. */
  279. abstract class fcUserApp extends fcDataApp {
  280.  
  281. // -- STATUS -- //
  282. // ++ SESSIONS ++ //
  283.  
  284. protected function GetSessionsClass() : string { return KS_CLASS_USER_SESSIONS; }
  285.  
  286. public function GetSessionTable() {
  287. $db = $this->GetDatabase();
  288. $sClass = $this->GetSessionsClass();
  289. return $db->MakeTable($sClass);
  290. }
  291. public function GetSessionStatus() : cIOResult {
  292. return $this->GetSessionTable()->GetIt()->MakeActiveRecord();
  293. }
  294.  
  295. // -- SESSIONS -- //
  296. // ++ USERS ++ //
  297.  
  298. protected function GetUsersClass() : string { return \ferret\users\ctUserAccts::class; }
  299.  
  300. public function UserData($id=NULL) : cTableResult {
  301. throw new \exception('2021-01-14 Call UserTable() or UserRecord().');
  302. $db = $this->GetDatabase();
  303. return $db->MakeTableWrapper($this->GetUsersClass(),$id);
  304. }
  305. public function UserTable() : cTableResult {
  306. $db = $this->GetDatabase();
  307. return $db->MakeTable($this->GetUsersClass());
  308. }
  309. public function UserRecord(int $id) : cRecordResult {
  310. $db = $this->GetDatabase();
  311. return $db->MakeRow($this->GetUsersClass(),$id);
  312. }
  313.  
  314. /*----
  315.   RETURNS: status that indicates whether the current session has a logged-in user or not
  316.   ASSUMES: There is always a current session, and it has been updated.
  317.   NOTE: This is the app global, so we're never interested in users aside from the current active one.
  318.   TODO: rename to CurrentUserStatus()
  319.   */
  320. public function UserStatus() : cIOResult {
  321. $osSess = $this->GetSessionStatus();
  322. $osUser = $osSess->Record()->GetIt()->LoggedInUserStatus();
  323. return $osUser;
  324. }
  325. /*----
  326.   RETURNS: TRUE iff the user is logged in
  327.   NOTE: This does not alias to UserStatus()->GetHasRow() because
  328.   that requires retrieving the user record, which we don't need
  329.   to do. All we need here is whether the session record has a User ID set.
  330.   */
  331. public function UserIsLoggedIn() : bool { return $this->GetSessionStatus()->Record()->GetIt()->UserIsLoggedIn(); }
  332. public function CurrentUserCan(string $sPerm) : bool {
  333. $osrcUser = $this->UserStatus()->Record();
  334. if ($osrcUser->HasIt()) {
  335. $rcUser = $osrcUser->GetIt();
  336. return $rcUser->CanDo(self::Globals()->Perm_CanViewAllNodes());
  337. } else {
  338. return FALSE;
  339. }
  340. }
  341.  
  342. // ++ USERS ++ //
  343. }
  344. /*::::
  345.   ABSTRACT: n/i - GetDatabase(), GetPageClass(), GetKioskClass()
  346.   HISTORY:
  347.   2019-08-17 moved GetAppKeyString() into fcGlobals as an abstract
  348.   2020-01-18 adding OData access methods
  349. */
  350. abstract class fcAppStandard extends fcUserApp {
  351.  
  352. // ++ MAIN ++ //
  353.  
  354. public function Go() {
  355. try {
  356. $this->Main();
  357. } catch(\ferret\except\cBase | \ferret\except\cDebug $e) {
  358. echo $e->React();
  359. } catch(Throwable $e) {
  360. $code = $e->getCode();
  361. $sNative = ($code==0)?'':"<b>Native error $code</b> caught:";
  362. $ex = new \ferret\except\cWrapper($e);
  363.  
  364. echo '<html><head><title>'
  365. .self::Globals()->GetText_SiteName_short()
  366. .' native error catch</title></head><body>'
  367. /*
  368.   .$sNative
  369.   .'<ul>'
  370.   .'<li><b>Error message</b>: '.$e->getMessage().'</li>'
  371.   .'<li><b>Thrown in</b> '.$e->getFile().' <b>line</b> '.$e->getLine()
  372.   .'<li><b>Stack trace</b>:'.nl2br($e->getTraceAsString()).'</li>'
  373.   .'</ul>'
  374.   */
  375. .$ex->MessageToShow()
  376. .'<br><small>Caught '.get_class($e).' code '.$e->getCode().' in '.__FILE__.' line '.__LINE__.'</small>'
  377. .'</body></html>'
  378. ;
  379. // TO DO: generate an email as well (2019-08-17 It was doing this awhile back, but I disabled it somewhere because self-spam?)
  380. }
  381. }
  382. public function ReportSimpleError(string $s) {
  383. $this->DoEmail_fromAdmin_Auto(
  384. static::Globals()->GetEmailAddr_forErrors(),
  385. static::Globals()->GetEmailName_forErrors(),
  386. 'Silent Internal Error',$s);
  387. // TODO: log it also?
  388. }
  389. protected function Main() {
  390. $db = $this->GetDatabase();
  391. $db->Open();
  392. if ($db->Action()->GetOkay()) {
  393. $oPage = $this->GetPageObject();
  394. $oPage->DoBuilding();
  395. $oPage->DoFiguring();
  396. $oPage->DoOutput();
  397. $db->Shut();
  398. } else {
  399. throw new fcDebugException('Ferreteria Config Error: Could not open the database.');
  400. }
  401. }
  402.  
  403. // -- MAIN -- //
  404. // ++ PROFILING ++ //
  405.  
  406. private $fltStart;
  407. public function SetStartTime() { $this->fltStart = microtime(true); }
  408. /*----
  409.   RETURNS: how long since StartTime() was called, in microseconds
  410.   */
  411. protected function ExecTime() { return microtime(true) - $this->fltStart; }
  412.  
  413. // -- PROFILING -- //
  414. // ++ CLASSES ++ //
  415.  
  416. /*
  417.   NOTE: These should return the non-admin versions of classes.
  418.   fcAppStandardAdmin will override with admin classes.
  419.   */
  420.  
  421. /* 2020-02-02 not needed anymore
  422.   abstract protected function GetEventsClass();
  423.   */
  424. abstract protected function GetODataClass() : string;
  425. protected function GetDropinManagerClass() : string { return ferret\ctDropInManager::class; }
  426.  
  427. // -- CLASSES -- //
  428. // ++ TABLES ++ //
  429.  
  430. protected function LogDataClass() : string { return \ferret\odata\cLogData::class; }
  431. private $oLog = NULL;
  432. public function LogData() : \ferret\odata\cLogData {
  433. if (is_null($this->oLog)) {
  434. $sClass = $this->LogDataClass();
  435. $this->oLog = new $sClass($this->GetDatabase());
  436. }
  437. return $this->oLog;
  438. }
  439. public function ODataTable() : cTableResult {
  440. $db = $this->GetDatabase();
  441. return $db->MakeTableWrapper($this->GetODataClass());
  442. }
  443.  
  444. // -- TABLES -- //
  445. // ++ LOGGING ++ //
  446.  
  447. public function CreateEvent($sCode,$sText,array $arData=NULL) {
  448. throw new exception('2020-03-11 No longer using this way of creating events.');
  449. // Use tLoggable* trait and call $this->CreateEvent(), or $this->LogData()->Nodes()->CreateEvent()
  450. $t = $this->EventData()->GetTable();
  451. $id = $t->CreateBaseEvent($sCode,$sText,$arData);
  452. return $id;
  453. }
  454. public function FinishEvent($idEvent,$sState,$sText=NULL,array $arData=NULL) {
  455. throw new exception('2019-08-05 Is anything still using this?');
  456. $t = $this->EventTable_Done();
  457. $t->CreateRecord($idEvent,$sState,$sText,$arData);
  458. }
  459.  
  460. // -- LOGGING -- //
  461. // ++ ACTION ++ //
  462.  
  463. // CEMENT
  464.  
  465. public function AddContentString(string $s) {
  466. $oPage = $this->GetPageObject();
  467. $oPage->GetElement_PageContent()->AddText($s);
  468. }
  469.  
  470. // -- ACTION -- //
  471. // ++ EMAIL ++ //
  472.  
  473. // TODO: rename to GetEmailAddress_FROM()
  474. protected function EmailAddr_FROM(string $sTag) {
  475. $ar = array('tag'=>$sTag);
  476. $tplt = new fcTemplate_array(
  477. \fcApp::Globals()->GetTemplateVariableOpen(),
  478. \fcApp::Globals()->GetTemplateVariableShut(),
  479. $ar
  480. );
  481.  
  482. return $tplt->Replace(self::Globals()->GetTemplate_EmailAddress_Admin());
  483. }
  484. /*----
  485.   ACTION: send an automatic email (i.e. not a message from an actual person)
  486.   USAGE: called from clsEmailAuth::SendPassReset_forAddr()
  487.   */
  488. /* 2020-11-17 presumably no longer needed
  489.   public function DoEmail_fromAdminAuto_OLD(string $sToAddr,string $sToName,string $sSubj,string $sMsg) {
  490.   if (empty($sToName)) {
  491.   $sAddrToFull = $sToAddr;
  492.   } else {
  493.   $sAddrToFull = $sToName.' <'.$sToAddr.'>';
  494.   }
  495.  
  496.   $sHdr = 'From: '.$this->EmailAddr_FROM(date('Y'));
  497.   $ok = mail($sAddrToFull,$sSubj,$sMsg,$sHdr);
  498.   return $ok;
  499.   } */
  500. /*----
  501.   ACTION: send an automatic administrative email
  502.   USAGE: called from vcPageAdmin::SendEmail_forLoginSuccess()
  503.   TODO: should probably return cActionStatus (or similar) instead.
  504.   */
  505. public function DoEmail_fromAdmin_Auto(string $sToAddr,string $sToName,string $sSubj,string $sMsg) : bool {
  506. if ($this->UserIsLoggedIn()) {
  507. $idUser = $this->StatusOfUserID()->GetIt();
  508. $sTag = 'user-'.$idUser;
  509. } else {
  510. $sTag = date('Y');
  511. }
  512.  
  513. $oSpec = new ferret\strings\cTemplateSpec_standard(
  514. self::Globals()->GetTemplate_EmailAddress_Admin()
  515. );
  516. $oTplt = new \ferret\strings\cTemplate_array($oSpec);
  517. /*
  518.   $oTplt = new \ferret\strings\cTemplate_array(
  519.   \fcApp::Globals()->GetTemplateVariableOpen(),
  520.   \fcApp::Globals()->GetTemplateVariableShut(),
  521.   self::Globals()->GetTemplate_EmailAddress_Admin()
  522.   );
  523.   */
  524. $oTplt->SetCells(
  525. ['tag' => $sTag]
  526. );
  527.  
  528. $sAddrFrom = $oTplt->Render();
  529. if (empty($sToName)) {
  530. $sAddrToFull = $sToAddr;
  531. } else {
  532. $sAddrToFull = $sToName.' <'.$sToAddr.'>';
  533. }
  534.  
  535. $sHdr = 'From: '.$sAddrFrom;
  536. $ok = mail($sAddrToFull,$sSubj,$sMsg,$sHdr);
  537.  
  538. $arData = array(
  539. 'action' => 'admin email sent',
  540. 'to-addr' => $sToAddr,
  541. 'to-name' => $sToName,
  542. 'subject' => $sSubj,
  543. 'message' => $sMsg
  544. );
  545. #$this->CreateEvent(KS_EVENT_FERRETERIA_EMAIL_SENT,'admin email sent',$arData);
  546. $os = $this->LogData()->Nodes()->CreateEvent(KS_EVENT_FERRETERIA_EMAIL_SENT,$arData); // cInsertStatus
  547.  
  548. if (!$os->Action()->GetOkay()) {
  549. // 2020-03-11 honestly not sure what should happen here but this at least seems like a good idea:
  550. $ok = FALSE;
  551. }
  552.  
  553. return $ok;
  554. }
  555.  
  556. // -- EMAIL -- //
  557.  
  558. }
  559. /*----
  560.   PURPOSE: standard App class with basic functionality, no admin stuff
  561.   HISTORY:
  562.   2018-04-27 created for better handling of Event classes
  563.   2019-06-15 adding Kiosk class because everything seems to need that, one way or another
  564.   ABSTRACT: needs GetPageClass(), GetDatabase()
  565. */
  566. abstract class fcAppStandardBasic extends fcAppStandard {
  567.  
  568. // ++ CLASSES ++ //
  569.  
  570. protected function GetEventsClass() : string { return fctEventPlex_standard::class; }
  571. protected function GetODataClass() : string { return \ferret\odata\ctNodeLogic::class; }
  572. protected function GetKioskClass() : string { return \fcMenuKiosk::class; }
  573.  
  574. // -- CLASSES -- //
  575. }
  576. /*----
  577.   PURPOSE: standard App class with admin functionality
  578.   HISTORY:
  579.   2018-04-27 created for better handling of Event classes
  580. */
  581. abstract class fcAppStandardAdmin extends fcAppStandard {
  582.  
  583. // ++ CLASSES ++ //
  584.  
  585. /* 2020-02-02 no longer needed
  586.   protected function GetEventsClass() {
  587. return KS_CLASS_EVENT_LOG_ADMIN;
  588.   } */
  589. protected function LogDataClass() : string { return \ferret\odata\cAdminLogData::class; }
  590. protected function GetODataClass() : string { return KS_CLASS_ADMIN_NODE_CORE; }
  591. // OVERRIDE
  592. protected function GetSessionsClass() : string {
  593. if (ferret\ctDropInManager::Me()->HasDropinModule('ferret.users')) {
  594. return KS_CLASS_ADMIN_USER_SESSIONS; // admin features
  595. } else {
  596. return parent::GetSessionsClass(); // basic logic
  597. }
  598. }
  599. // OVERRIDE
  600. protected function GetUsersClass() : string { return KS_CLASS_ADMIN_USER_ACCOUNTS; }
  601.  
  602. // -- CLASSES -- //
  603. }
  604.