Edit via SFTP
  1. <?php namespace ferret;
  2. /*
  3.   PURPOSE: drop-in module system
  4.   HISTORY:
  5.   2013-11-28 started
  6.   2014-12-27 split into two classes (one for each drop-in, and one for the drop-in manager)
  7.   2017-01-01 classes now descend from fcTable_wRecords (was fcDataSource) and fcSourcedDataRow
  8.   2017-04-09 evidently this was later changed to fcDataTable_array / fcDataRow_array
  9.   2019-12-25 and now it's cArrayTable, though I forget exactly when I did that (sometime in the past couple of weeks)
  10.   2020-12-06 lots of refactoring... trying to eliminate the use of NULL in menu arrays
  11.   2021-02-22 adding namespace ferret
  12.   2021-08-28 I overrode WorkSpaceClass() to return cAutoSpace::class, on the theory that ctDropInManager
  13.   doesn't actually use databases, tables, etc. but it turns out that cTabloid, the base type for all Tables,
  14.   does need a database and I think also we need that here too for spawning ActionObjects or whatever they're
  15.   called ...so, removing the override.
  16. */
  17.  
  18. #define('KS_DROPIN_FIELD_FEATURES','features');
  19.  
  20. class ctDropInManager extends data\cArrayTable {
  21. #use data\tDMapForTable;
  22. use data\bank\tForTable;
  23.  
  24. // ++ STATIC ++ //
  25.  
  26. // METHOD: Goes through App object-factory so we don't get more than one regardless of how it's requested.
  27. static public function Me() { return \fcApp::Me()->GetDropinManager(); }
  28.  
  29. // -- STATIC -- //
  30. // ++ CONFIG ++ //
  31.  
  32. protected function SingleRowClass() : string { return crcDropIndex::class; }
  33. protected function PortBankClass() : string { return data\cPortBankStub::class; }
  34.  
  35. // -- CONFIG -- //
  36. // ++ FAMILY ++ //
  37.  
  38. private $rs = NULL;
  39. protected function GetRows() {
  40. if (is_null($this->rs)) {
  41. $this->rs = $this->SpawnRows();
  42. }
  43. return $this->rs;
  44. }
  45.  
  46. // -- FAMILY -- //
  47. // ++ MODULES ++ //
  48.  
  49. // PURPOSE: So we can display info about dropin libraries only
  50. protected function AddDropinModule(crcDropIndex $oDex) {
  51. $this->GetRows()->SetRowObject($oDex->GetNameString(),$oDex);
  52. }
  53. protected function HasDropinModules() : bool { return ($this->GetRows()->HasRows()); }
  54. // PUBLIC because outside callers sometimes need to know if a module exists or not
  55. public function HasDropinModule(string $sName) : bool {
  56. if (!$this->HasDropinModules()) {
  57. $sError = 'attempting to query dropin modules when none have been found.';
  58. $e = new \ferret\except\cUsage($sError);
  59. throw $e;
  60. }
  61. return $this->GetRows()->GivenRow($sName)->HasIt();
  62. }
  63.  
  64. // -- MODULES -- //
  65. // ++ BASIC OPERATIONS ++ //
  66.  
  67. /*----
  68.   ACTION: scans the given folder and adds any drop-in modules found
  69.   HISTORY:
  70.   2019-10-29 Adding optional $sLibName parameter so we can create a Library at this point,
  71.   without possibly causing conflict if the app has multiple dropin folders.
  72.   2019-11-03 Removing $sLibName because each dropin should be a Library, and we can get
  73.   the name from the index file.
  74.   */
  75. static public function ScanDropins(string $fsFolder,\fcTreeNode $oMenu) : void {
  76. if (!file_exists($fsFolder)) {
  77. throw new exception('Dropins folder "'.$fsFolder.'" does not exist.');
  78. }
  79. $oMgr = self::Me();
  80. $oMgr->CheckFolders($fsFolder,$oMenu);
  81. }
  82. protected function CheckFolders(string $fsFolder,\fcTreeNode $oMenu) : void {
  83. $poDir = dir($fsFolder);
  84. $ar = NULL;
  85. while (FALSE !== ($fnFile = $poDir->read())) {
  86. if (($fnFile!='.') && ($fnFile!='..')) {
  87. $fs = $fsFolder.'/'.$fnFile;
  88. if (is_dir($fs)) {
  89. // save in an array so we can sort before loading
  90. $ar[$fnFile] = $fs;
  91. }
  92. }
  93. }
  94. if (is_null($ar)) {
  95. throw new exception("No subfolders were found in Dropin folder '$fsFolder'.");
  96. }
  97. ksort($ar);
  98. foreach ($ar as $fn => $fp) {
  99. $this->CheckFolder($fp,$oMenu);
  100. }
  101. if (!$this->HasDropinModules()) {
  102. throw new exception("No Dropins were found in '$fsFolder'.");
  103. }
  104. }
  105. /*----
  106.   ACTION: check the given folder for a dropin index; if found, load it.
  107.   OUTPUT: dropin object added to internal list
  108.   HISTORY:
  109.   2019-11-03 The module index used to need $oRoot (set to $oMenu), but I changed that
  110.   a few months back. Module indices are now just a complex array.
  111.   2019-11-04 Merged ProcessIndex() back into here, so I could pass base path ($fsFolder) to $od more easily
  112.   */
  113. protected function CheckFolder(string $fpFolder,\fcTreeNode $oMenu) : void {
  114. $od = $this->SpawnRow(); // $od is crcDropIndex
  115.  
  116. $od->SetFolderPath($fpFolder);
  117. $od->SetMenu($oMenu); // required by ProcessIndex()
  118. //$od->SetFileName(KFN_DROPIN_INDEX);
  119.  
  120. //if (is_file($fsIndex)) {
  121. $ok = $od->Processindex();
  122. if ($ok) {
  123. $this->AddDropinModule($od);
  124. }
  125. }
  126.  
  127. // -- BASIC OPERATIONS -- //
  128. }
  129.  
  130. /*::::
  131.   PURPOSE: encompasses everything referenced within a dropin index file
  132.   In theory, there could be multiple indexes in a single folder...
  133.   (which would require either multiple module-definitions in a single index
  134.   or multiple index files, which would require explicitly loading one of them)
  135.   ...but in practice there's been no reason to ever do this. One folder = one module.
  136.  
  137.   A DropIndex corresponds to a ClassLoader-library because:
  138.   - both have a name
  139.   - both can contain multiple classes
  140.   - both have indexes
  141.  
  142.   I was trying to think how to handle the descent so that a DropIndex could somehow descend
  143.   from both ArrayRows and cLibrary, but then it occurred to me that the DropIndex class
  144.   should simply have a Library property.
  145.   FIELDS (see FIELD VALUES section):
  146.   [name]: short name for index (library)
  147.   [descr]: one-line description
  148.   [version]: version number (can be non-numeric)
  149.   [date]: release date in YYYY/MM/DD format
  150.   [URL]: URL for more information about the library
  151.   HISTORY:
  152.   2019-11-30 Re-parenting yet again, to see what fx() we actually need from new-new structure
  153.   cArrayRow -> cSingleRow -> cMultiRow -> cMultiRowSerial -> cMultiRowArray -> cSingleRow
  154.   2020-11-11 merging ftDropIndex into crcDropIndex, because I can't see why it was separate in the first palce.
  155. */
  156. class crcDropIndex extends data\cInternalOneRowOneKey {
  157.  
  158. // ++ SETUP ++ //
  159.  
  160. protected function OnSpace() : void {} // STUB, for now
  161.  
  162. protected function GetKeyName() : string { return 'name'; }
  163.  
  164. // -- SETUP -- //
  165. // ++ INTERNAL VALUES ++ //
  166.  
  167. private $oLib;
  168. public function SetLibrary(cDropLibrary $oLib) { $this->oLib = $oLib; }
  169. protected function GetLibrary() : cDropLibrary { return $this->oLib; }
  170.  
  171. /*----
  172.   PROPERTY: FolderPath = path to the dropin's base folder. Must contain index file.
  173.   NOTES:
  174.   2019-11-12 what context was I referring to when I said "must be stored in row array, because object is not preserved"?
  175.   Right now, we're trying to set it before the first row has been loaded, so storing it there won't work.
  176.   HISTORY:
  177.   2020-11-10 made GetFolderPath() public so we can show it to the user
  178.   */
  179. private $fpFolder = 'INTERNAL ERROR! see '.__FILE__.' line '.__LINE__;
  180. public function SetFolderPath(string $fp) { $this->fpFolder = $fp; }
  181. // PUBLIC for self-reporting
  182. public function GetFolderPath() : string { $fp = $this->fpFolder; return $fp; }
  183.  
  184. /*----
  185.   RETUNS: full filespect to index file
  186.   */
  187. protected function GetIndexFileSpec() : string {
  188. $fp = $this->GetFolderPath();
  189. return $fp.'/'.\ferret\globals\cAbstract::Me()->GetFileName_forDropinIndex();
  190. }
  191. public function IndexFileStatus() : \ferret\cFileStatus {
  192. $os = new \ferret\cFileStatus();
  193. $fs = $this->GetIndexFileSpec();
  194. $os->Spec()->SetIt($fs);
  195. #$os->SetExists(file_exists($fs));
  196. return $os;
  197. }
  198.  
  199. private $oMenu;
  200. public function SetMenu(\fcTreeNode $oMenu) { $this->oMenu = $oMenu; }
  201. protected function GetMenu() : \fcTreeNode { return $this->oMenu; }
  202.  
  203. // -- INTERNAL VALUES -- //
  204. // ++ DATA ++ //
  205.  
  206. /*
  207.   HISTORY:
  208.   2019-11-10 This can't be done all at once:
  209.   $this->SetFieldValues($arSpecs);
  210.   $this->RegisterClasses();
  211.   $this->RegisterSection();
  212.   They were all together in SetSpecs(), but stuff needs to happen betweem the first and second items...
  213.   ...so I brought them back here into the function which used to call SetSpecs().
  214.   */
  215. public function ProcessIndex() : bool {
  216. $ok = FALSE;
  217. $os = $this->IndexFileStatus();
  218. if ($os->Exists()) {
  219. $fsIndex = $os->Spec()->GetIt();
  220. #echo "DROPIN FS: [$fsIndex]<br>";
  221.  
  222. // PROCESS THE INDEX
  223. // INPUT: nothing
  224. require($fsIndex); // load the module index
  225. // OUTPUT: $arDropin
  226.  
  227. if (isset($arDropin)) {
  228. //$this->SetSpecs($arDropin);
  229. $this->SetCells($arDropin);
  230. $oLib = new cDropLibrary($this);
  231. $this->SetLibrary($oLib);
  232.  
  233. $this->RegisterClasses();
  234. $this->RegisterSection();
  235. } else {
  236. throw new error("Dropin error: $fsIndex does not define \$arDropin.");
  237. }
  238. $ok = TRUE;
  239. }
  240. return $ok;
  241. }
  242.  
  243. /*----
  244.   ACTION: register any classes defined within the dropin.
  245.   */
  246. protected function RegisterClasses() {
  247. $ar = $this->GetClassArray();
  248. $oLib = $this->GetLibrary();
  249. foreach ($ar as $sFile => $sClasses) {
  250. $om = $oLib->MakeModule($sFile);
  251. if (is_array($sClasses)) {
  252. // value is an array of class names for file $sFile
  253. foreach ($sClasses as $sClass) {
  254. $om->AddClass($sClass);
  255. }
  256. } else {
  257. // assume value is a single class name
  258. $om->AddClass($sClasses);
  259. }
  260. }
  261. }
  262. protected function RegisterSection() {
  263. $arSect = $this->GetSectionArray(); // from index.dropin file
  264. $db = $this->GetDatabase();
  265. #echo 'SECTION ARRAY:'.\fcArray::Render($arSect);
  266. $oa = \fcMenuFolder::CreateCollection();
  267. $oa->LinkText()->SetFromArray($arSect,'title');
  268. // 2020-12-07 what is/was this for?
  269. #$oa->PageTitle()->SetFromArray($arSect,'title-long');
  270. $oa->Popup()->SetFromArray($arSect,'popup');
  271. // 2020-12-07 what is/was this for?
  272. #$oa->ActorClass()->SetFromArray($arSect,'class');
  273.  
  274. #echo 'SECTION:'.\fcArray::Render($arSect);
  275.  
  276. // create root entry for this dropin:
  277. $om = $this->GetMenu()->SetNode(new \fcMenuFolder($oa));
  278. #echo "OM class: ".get_class($om).'<br>';
  279.  
  280. $fpApp = \fcApp::Me()->GetKioskObject()->GetAbsolutePath(TRUE); // get base path (TRUE = with ending slash)
  281.  
  282. // MENU SETUP
  283.  
  284. $arActs = $arSect['actions'];
  285. $oa = new \ferret\value\cDropin();
  286. $oa->BasePath()->SetIt($fpApp);
  287. $arBase = $oa->GetStati();
  288.  
  289. foreach ($arActs as $sAct => $arArgs) {
  290. $oa->SetStati($arBase);
  291. $oa->ChoiceValue()->SetIt($sAct);
  292. $oa->Value()->SetFromArray($arArgs,'text');
  293. $oa->ActorClass()->SetFromArray($arArgs,'class');
  294. $oa->Popup()->SetFromArray($arArgs,'summary');
  295. if (array_key_exists('text',$arArgs)) {
  296. #$arMenu['kname'] = 'do'; // 2020-12-06 Where was this being set before?
  297. # $oa->ChoiceKey()->SetValue('do');
  298. $odl = new \fcDropinLink($oa);
  299. } else {
  300. $odl = new \fcDropinAction($oa);
  301. }
  302. $odl->SetDatabase($db);
  303. $omi = $om->AddNode($odl);
  304. if (array_key_exists('perms',$arArgs)) {
  305. $omi->SetRequiredPrivilege($arArgs['perms']);
  306. }
  307. }
  308.  
  309. // DEBUGGING
  310. #$omm = $this->GetMenu();
  311. #echo 'MENU STRUCTURE:'.$omm->DumpHTML();
  312. }
  313.  
  314. // -- DATA -- //
  315. // ++ FIELD VALUES ++ //
  316.  
  317. public function GetNameString() : string { return $this->CellStatus('name')->GetIt(); }
  318. public function GetVersionString() : string { return $this->GetFieldValue('version'); }
  319. public function GetDateString() : string { return $this->GetFieldValue('date'); }
  320. public function URLStatus() : \ferret\cValueReadOnly { return $this->CellStatus('URL'); }
  321. protected function GetClassArray() : array { return $this->CellStatus('classes')->GetIt(); }
  322. protected function GetSectionArray() : array { return $this->CellStatus('section')->GetIt(); }
  323.  
  324. // -- FIELD VALUES -- //
  325. // ++ FIELD CALCULATIONS ++ //
  326.  
  327. public function GetNameHTML() : string {
  328. $sName = $this->GetNameString();
  329. $osResURL = $this->URLStatus();
  330. if ($osResURL->IsBlank()) {
  331. $out = $sName;
  332. } else {
  333. $url = $osResURL->value;
  334. $out = "<a href='$url'>$sName</a>";
  335. }
  336. return $out;
  337. }
  338. protected function GetDatabase() : \ferret\data\cDatabase {
  339. $os = $this->CellStatus('db.class');
  340. if ($os->HasIt()) {
  341. $sClass = $os->GetIt();
  342. $oSpec = new $sClass();
  343. $db = \ferret\data\cDatabase::Instantiate($oSpec,FALSE); // error on failure
  344. return $db;
  345. } else {
  346. return \fcApp::Me()->GetDatabase();
  347. }
  348. }
  349.  
  350. // -- FIELD CALCULATIONS -- //
  351. }
  352. class cDropLibrary extends classloader\cLibraryBase {
  353. // ACTION: add this library to the current dropin index
  354. public function __construct(crcDropIndex $oIndex) { $this->SetIndex($oIndex); }
  355.  
  356. // ++ CONFIG ++ //
  357.  
  358. private $oIndex;
  359. protected function SetIndex(crcDropIndex $oIndex) : void {
  360. $this->oIndex = $oIndex;
  361. $this->SetLibName($oIndex->GetNameString());
  362. }
  363. protected function GetIndex() : crcDropIndex { return $this->oIndex; }
  364.  
  365. protected function LoadNow() {
  366. throw new exception('2020-11-10 How does this get called?');
  367. #$this->GetIndex()->
  368. }
  369.  
  370. // -- CONFIG -- //
  371. // ++ PROPERTIES ++ //
  372.  
  373. // CEMENT
  374. public function GetFileSpec() : string {
  375. $os = $this->GetIndex()->IndexFileStatus();
  376. if (!$os->Exists()) {
  377. throw new exception('Ferreteria internal error: processed dropin index not found at load time.'); //...I think
  378. }
  379. return $os->Spec()->GetIt();
  380. }
  381.  
  382. }
  383.