PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/forms.class.php

https://github.com/uCore/uCore
PHP | 3080 lines | 2314 code | 423 blank | 343 comment | 721 complexity | 9241f4cf853f563d49e48ce58fa196ae MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, GPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //-- debugging
  3. //define('SHOW_QUERY' ,false);
  4. //-- InputType
  5. define('itNONE' ,'');
  6. define('itBUTTON' ,'button');
  7. define('itSUBMIT' ,'submit');
  8. define('itRESET' ,'reset');
  9. define('itCHECKBOX' ,'checkbox');
  10. define('itOPTION' ,'option');
  11. define('itPASSWORD' ,'password');
  12. define('itPLAINPASSWORD','plain_password');
  13. define('itTEXT' ,'text');
  14. define('itTEXTAREA' ,'textarea');
  15. define('itCOMBO' ,'combo');
  16. define('itLISTBOX' ,'listbox');
  17. define('itFILE' ,'file');
  18. define('itDATE' ,'date');
  19. define('itTIME' ,'time');
  20. define('itDATETIME' ,'datetime');
  21. //-- FilterCompareType
  22. define('ctCUSTOM' ,'{custom}');
  23. define('ctIGNORE' ,'{ignore}');
  24. define('ctMATCH' ,'{MATCH}');
  25. define('ctMATCHBOOLEAN' ,'{MATCH_BOOLEAN}');
  26. define('ctANY' ,'');
  27. define('ctEQ' ,'=');
  28. define('ctNOTEQ' ,'!=');
  29. define('ctLT' ,'<');
  30. define('ctGT' ,'>');
  31. define('ctLTEQ' ,'<=');
  32. define('ctGTEQ' ,'>=');
  33. define('ctLIKE' ,'LIKE');
  34. define('ctNOTLIKE' ,'NOT LIKE');
  35. define('ctIN' ,'IN');
  36. define('ctIS' ,'IS');
  37. define('ctISNOT' ,'IS NOT');
  38. define('ctREGEX' ,'REGEXP');
  39. define('ctBETWEEN' ,'BETWEEN');
  40. //-- Filter Sections
  41. define('FILTER_HAVING' ,'having');
  42. define('FILTER_WHERE' ,'where');
  43. //-- Date Formats
  44. // date
  45. // define("SQL_FORMAT_DATE" ,'%Y-%m-%d');
  46. // define("SQL_FORMAT_TIME" ,'%H:%i:%s');
  47. // define("SQL_FORMAT_DATETIME" ,SQL_FORMAT_DATE.' '.SQL_FORMAT_TIME);
  48. // define("SQL_FORMAT_EMPTY_DATE" ,'00/00/0000');
  49. // define("PHP_FORMAT_DATE" ,'d/m/Y'); // used with date(), but strftime() uses the SQL format
  50. // timestamp
  51. // define("SQL_FORMAT_TIMESTAMP" ,'%d/%m/%Y %H:%i:%s');
  52. // define("SQL_FORMAT_EMPTY_TIMESTAMP" ,'00/00/0000 00:00:00');
  53. // define("PHP_FORMAT_TIMESTAMP" ,'d/m/Y H:i:s'); // used with date(), but strftime() uses the SQL format
  54. // Javascript date/time regex:
  55. // datetimeRegex = new RegExp("([0-9]{2})/([0-9]{2})/([0-9]{4})( ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?)?");
  56. // datetimeRegex = new RegExp("([0-9]{2})/([0-9]{2})/([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?|([0-9]{2})/([0-9]{2})/([0-9]{4})|([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?");
  57. // define('SQL_DATETIME_REGEX' ,"(([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?)");
  58. // define('SQL_DATE_REGEX' ,"(([0-9]{4})-([0-9]{2})-([0-9]{2}))");
  59. // define('SQL_TIME_REGEX' ,"(([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?)");
  60. // define('DATETIME_REGEX' ,"((([0-9]{2})/([0-9]{2})/([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?)|(([0-9]{2})/([0-9]{2})/([0-9]{4}))|(([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?))");
  61. // MODULE OPTIONS
  62. define('ALWAYS_ACTIVE',flag_gen());
  63. define('INSTALL_INACTIVE',flag_gen());
  64. define('NO_HISTORY',flag_gen());
  65. define('DISTINCT_ROWS',flag_gen());
  66. define('ALLOW_FILTER',flag_gen());
  67. define('ALLOW_EDIT',flag_gen());
  68. define('ALLOW_ADD',flag_gen());
  69. define('ALLOW_DELETE',flag_gen());
  70. define('NO_NAV',flag_gen());
  71. define('PERSISTENT',flag_gen());
  72. define('PERSISTENT_PARENT',flag_gen());
  73. define('SHOW_FUNCTIONS',flag_gen());
  74. define('SHOW_TOTALS',flag_gen());
  75. define('LIST_HIDE_HEADER',flag_gen());
  76. define('LIST_HIDE_STATUS',flag_gen());
  77. define('DEFAULT_OPTIONS',ALLOW_FILTER);
  78. // START CLASSES
  79. class uFilter {
  80. public $filter;
  81. public $compareType;
  82. public $inputType;
  83. public $value;
  84. public function __construct($filter,$compareType,$inputType,$value) {
  85. $this->filter = $filter;
  86. $this->compareType = $compareType;
  87. $this->inputType = $inputType;
  88. $this->value = $value;
  89. }
  90. }
  91. class uDataset {
  92. protected $module = null;
  93. protected $query = null;
  94. protected $countQuery = null;
  95. protected $args = array();
  96. protected $recordCount = null;
  97. public function __construct($module,$filter,$clearFilters) {
  98. // initialise count
  99. $this->module = $module;
  100. $this->module->_SetupFields();
  101. if ($filter===NULL) $filter = array();
  102. if (isset($filter) && !is_array($filter)) $filter = array($this->module->GetPrimaryKey()=>$filter);
  103. $fltrs = $this->module->filters;
  104. if ($clearFilters) $this->module->ClearFilters();
  105. foreach ($filter as $field => $val) {
  106. if ($val instanceof uFilter) {
  107. if (($fltr =& $this->module->GetFilterInfo($val->filter))) {
  108. $fltr['value'] = $val->value;
  109. } else {
  110. $this->module->AddFilter($val->filter,$val->compareType,$val->inputType,$val->value);
  111. }
  112. continue;
  113. }
  114. if (is_numeric($field)) { // numeric key is custom filter
  115. $this->module->AddFilter($val,ctCUSTOM);
  116. continue;
  117. }
  118. if (($fltr =& $this->module->GetFilterInfo($field))) { // filter uid exists
  119. $fltr['value'] = $val;
  120. continue;
  121. }
  122. $this->module->AddFilter($field,ctEQ,itNONE,$val);
  123. }
  124. $this->BuildSqlQuery($this->args);
  125. $this->module->filters = $fltrs;
  126. }
  127. public function BuildSqlQuery(&$args) {
  128. // GET SELECT
  129. $select = $this->module->GetSelectStatement();
  130. // GET FROM
  131. $from = ' FROM '.$this->module->GetFromClause();
  132. // GET WHERE
  133. $where = $this->module->GetWhereStatement($args); $where = $where ? "\n WHERE $where" : ''; // uses WHERE modifier
  134. // GET GROUPING
  135. $group = $this->module->GetGrouping(); $group = $group ? "\n GROUP BY $group" : '';
  136. // GET HAVING
  137. $having = $this->module->GetHavingStatement($args); $having = $having ? "\n HAVING $having" : ''; // uses HAVING modifier to account for aliases
  138. // GET ORDER
  139. $order = $this->module->GetOrderBy(); $order = $order ? "\n ORDER BY $order" : '';
  140. $having1 = $this->module->GetFromClause() ? $having : '';
  141. $order1 = $this->module->GetFromClause() ? $order : '';
  142. $union = '';
  143. if (isset($this->module->UnionModules) && is_array($this->module->UnionModules)) {
  144. foreach ($this->module->UnionModules as $moduleName) {
  145. $obj = utopia::GetInstance($moduleName);
  146. $obj->_SetupFields();
  147. $select2 = $obj->GetSelectStatement();
  148. $from2 = ' FROM '.$this->module->GetFromClause();
  149. $where2 = $obj->GetWhereStatement($args); $where2 = $where2 ? "\n WHERE $where2" : '';
  150. $group2 = $obj->GetGrouping(); $group2 = $group2 ? "\n GROUP BY $group2" : '';
  151. $having2 = $obj->GetHavingStatement($args);
  152. $having2 = $having2 ? $having.' AND ('.$having2.')' : $having;
  153. // if (!empty($having2)) $having2 = $having.' AND ('.$having2.')';
  154. // else $having2 = $having;
  155. $order2 = $obj->GetOrderBy(); $order2 = $order2 ? "\n ORDER BY $order2" : '';
  156. $union .= "\nUNION\n(SELECT $select2$from2$where2$group2$having2$order2)";
  157. }
  158. $union .= " $order";
  159. }
  160. $this->query = "(SELECT $select$from$where$group$having1$order1)$union";
  161. if ($having) $this->countQuery = "(SELECT COUNT(*) FROM ((SELECT $select$from$where$group$having1$order1)$union) AS `_`)";
  162. else $this->countQuery = "(SELECT COUNT(*) FROM (SELECT NULL$from$where$group ORDER BY NULL) as `_`)";
  163. }
  164. public function DebugDump() {
  165. return $this->query."\n".var_export($this->args,true);
  166. }
  167. public function CountPages($items_per_page=10) {
  168. return (int)ceil($this->CountRecords() / $items_per_page);
  169. }
  170. public function CountRecords() {
  171. if ($this->recordCount === NULL) {
  172. try {
  173. $this->recordCount = database::query($this->countQuery,$this->args)->fetchColumn();
  174. } catch (Exception $e) { return 0; }
  175. }
  176. return $this->recordCount;
  177. }
  178. public function GetFirst() {
  179. return $this->GetOffset(0,1)->fetch();
  180. }
  181. /* page is zero indexed */
  182. public function &GetPage($page, $items_per_page=10) {
  183. $limit = '';
  184. if ($items_per_page > 0) {
  185. if ($page >= $this->CountPages($items_per_page)) $page = 0;
  186. $start = $items_per_page * $page;
  187. return $this->GetOffset($start,$items_per_page);
  188. }
  189. return $this;
  190. }
  191. public function &GetOffset($offset,$count) {
  192. $ql = $this->query.' LIMIT ?,?';
  193. $qa = $this->args; $qa[] = intval($offset); $qa[] = intval($count);
  194. $this->ds =& database::query($ql,$qa);
  195. return $this;
  196. }
  197. protected $ds = null;
  198. public function fetch() { return $this->CreateRecord($this->ds()->fetch()); }
  199. public function fetchAll() { return $this->CreateRecords($this->ds()->fetchAll()); }
  200. public function &ds() {
  201. if ($this->ds === null) $this->ds = database::query($this->query,$this->args);
  202. return $this->ds;
  203. }
  204. public function CreateRecords($rows) {
  205. if (!isset($rows) || !is_array($rows)) return $rows;
  206. $rc = count($rows);
  207. for ($i = 0; $i < $rc; $i++) {
  208. $rows[$i] = $this->CreateRecord($rows[$i]);
  209. }
  210. return $rows;
  211. }
  212. public function CreateRecord($row) {
  213. if (!isset($row) || !is_array($row)) return $row;
  214. // make link tables into array
  215. foreach ($row as $field=>$val) {
  216. if (empty($val)) continue;
  217. if (!isset($this->module->fields[$field])) continue;
  218. $fieldData = $this->module->fields[$field];
  219. if (!isset($fieldData['vtable'])) continue;
  220. if (!is_subclass_of($fieldData['vtable']['tModule'],'iLinkTable')) continue;
  221. $row[$field] = explode("\x1F",$val);
  222. }
  223. return $row;
  224. }
  225. }
  226. /**
  227. * Basic Utopia module. Enables use of adding parents and module to be installed and run.
  228. * No field or data access is available, use uDataModule and its decendants.
  229. */
  230. abstract class uBasicModule implements iUtopiaModule {
  231. public static function Initialise() {}
  232. /* static $singleton = NULL;
  233. public static function &call($method) {
  234. $null = NULL;
  235. if (self::$singleton == NULL) { ErrorLog('Singleton not configured'); }//ErrorLog("Error Calling {$classname}->{$funcname}"); return $null;}
  236. if (!method_exists(self::$singleton,$method)) { return $null; }
  237. $stack = debug_backtrace();
  238. $args = array();
  239. if (isset($stack[0]["args"]))
  240. for($i=2; $i < count($stack[0]["args"]); $i++)
  241. $args[$i-2] = & $stack[0]["args"][$i];
  242. $call = array(self::$singleton,$funcname);
  243. $return = call_user_func_array($call,$args);
  244. return $return;
  245. }
  246. public static function __callStatic($name, $arguments) {
  247. // Note: value of $name is case sensitive.
  248. $instance = utopia::GetInstance(get_class($this));
  249. return call_user_func_array(array($instance,$name),$arguments);
  250. }*/
  251. public function flag_is_set($flag) {
  252. $options = $this->GetOptions();
  253. return flag_is_set($options,$flag);
  254. }
  255. public function GetOptions() { return DEFAULT_OPTIONS; }
  256. protected $bypassSecurity = false;
  257. public function BypassSecurity($flag) {
  258. $this->bypassSecurity = $flag;
  259. }
  260. public function GetTitle() { return get_class($this); }
  261. public function GetDescription() {}
  262. public function GetKeywords() {}
  263. protected $isSecurePage = false;
  264. public function SetSecure() {
  265. $this->isSecurePage = true;
  266. }
  267. public $isDisabled = false;
  268. public function DisableModule($message='') {
  269. if (!$message) $message = true;
  270. $this->isDisabled = $message;
  271. }
  272. public function AssertURL($http_response_code = 301, $currentOnly = true) {
  273. if (!$currentOnly || get_class($this) == utopia::GetCurrentModule()) {
  274. $url = $this->GetURL($_GET);
  275. $checkurl = $_SERVER['REQUEST_URI'];
  276. if (($this->isSecurePage && !utopia::IsRequestSecure()) || $checkurl !== $url) {
  277. $abs = '';
  278. if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') != $this->isSecurePage) {
  279. $layer = 'http';
  280. if ($this->isSecurePage) $layer .= 's';
  281. $abs = $layer.'://'.utopia::GetDomainName();
  282. }
  283. header('Cache-Control: no-store, no-cache, must-revalidate'); // don't cache redirects
  284. header('Location: '.$abs.$url,true,$http_response_code); die();
  285. }
  286. }
  287. }
  288. public $hasRun = false;
  289. public function _RunModule() {
  290. $this->AssertURL();
  291. if ($this->isDisabled) { echo $this->isDisabled; return; }
  292. // build linklist of children
  293. $children = utopia::GetChildren(get_class($this));
  294. foreach ($children as $child => $links) {
  295. $obj = utopia::GetInstance($child);
  296. foreach ($links as $info) {
  297. if ($obj->flag_is_set(ALLOW_ADD)
  298. && !$this->flag_is_set(ALLOW_ADD)
  299. && is_subclass_of($child,'uSingleDataModule')
  300. && ($info['parentField'] === NULL || $info['parentField'] === '*')) {
  301. $url = $obj->GetURL(array('_n_'.$obj->GetModuleId()=>'1'));
  302. utopia::LinkList_Add('list_functions:'.get_class($this),null,CreateNavButton('New '.$obj->itemName,$url,array('class'=>'new-item')),1);
  303. }
  304. }
  305. }
  306. // BEFORE
  307. ob_start();
  308. $beforeResult = uEvents::TriggerEvent('BeforeRunModule',$this);
  309. $beforeContent = ob_get_clean();
  310. if (utopia::UsingTemplate() && $beforeContent) $beforeContent = '<div class="module-container '.get_class($this).' BeforeRunModule">'.$beforeContent.'</div>';
  311. echo $beforeContent;
  312. if ($beforeResult === FALSE) return FALSE;
  313. // RUN
  314. ob_start();
  315. $result = $this->RunModule();
  316. $runContent = ob_get_clean();
  317. if (utopia::UsingTemplate() && $runContent) $runContent = '<div class="module-container '.get_class($this).' RunModule">'.$runContent.'</div>';
  318. echo $runContent;
  319. if ($result === FALSE) return false;
  320. $this->hasRun = true;
  321. // AFTER
  322. ob_start();
  323. $afterResult = uEvents::TriggerEvent('AfterRunModule',$this);
  324. $afterContent = ob_get_clean();
  325. if (utopia::UsingTemplate() && $afterContent) $afterContent = '<div class="module-container '.get_class($this).' AfterRunModule">'.$afterContent.'</div>';
  326. echo $afterContent;
  327. if ($afterResult === FALSE) return FALSE;
  328. }
  329. public $parentsAreSetup = false;
  330. public abstract function SetupParents();
  331. public function _SetupParents() {
  332. if ($this->parentsAreSetup) return;
  333. $this->parentsAreSetup = true;
  334. uEvents::TriggerEvent('BeforeSetupParents',$this);
  335. $this->SetupParents();
  336. uEvents::TriggerEvent('AfterSetupParents',$this);
  337. }
  338. public $parents = array();
  339. public function HasParent($parentModule) {
  340. $children = utopia::GetChildren($parentModule);
  341. return array_key_exists(get_class($this),$children);
  342. }
  343. public function HasChild($childModule) {
  344. $children = utopia::GetChildren(get_class($this));
  345. return array_key_exists($childModule,$children);
  346. }
  347. // parentModule =
  348. // sourceField = the field on the parent which the filter is taken from (generally the PK value of the current module)
  349. // destinationField = the field on the current module which the filter is set to.
  350. // parentField = the field which will be "linked from", if null or empty, a child button is created. allows easy injection to list forms.
  351. // EG: 'client list', array('client id'=>'client id'), 'client name' : applies a link from the "client name" field on the "client list" to open the current form where "client id" = "client id"
  352. // EG: 'client list', 'title', 'title id', 'title' : applies a link from the "title" field on the "client list" to open the current form where "title id" = "title"
  353. // EG: 'client form', 'client id', 'client id' : creates a button on the "client form" page to open the current form and where "client id" = "client id"
  354. // TODO: AddParent - overriden in data module
  355. // suggested change: sourceField, destinationField become an array of "filters", eg: array("title" => "title id")
  356. /**
  357. * Add a parent of this module, linked by a field.
  358. *
  359. * @param string $parentModule The classname of the parent
  360. * @param string|array optional $fieldLinks
  361. * @param string optional $parentField
  362. * @param string optional $text
  363. */
  364. public static function AddParent($parentModule,$fieldLinks=NULL,$parentField=NULL,$text=NULL) {
  365. if (isset($fieldLinks) && is_string($fieldLinks)) $fieldLinks = array(array('fromField'=>$fieldLinks,'toField'=>$fieldLinks,'ct'=>ctEQ));
  366. if (isset($fieldLinks) && is_array($fieldLinks) && !array_key_exists(0,$fieldLinks)) {
  367. if (array_key_exists('fromField',$fieldLinks) && array_key_exists('toField',$fieldLinks)) {
  368. $fieldLinks = array($fieldLinks);
  369. } else {
  370. // change from array(from>to) to new format
  371. $newFL = array();
  372. foreach ($fieldLinks as $from => $to) {
  373. $newFL[] = array('fromField'=>$from,'toField'=>$to,'ct'=>ctEQ);
  374. }
  375. $fieldLinks = $newFL;
  376. }
  377. }
  378. if (isset($fieldLinks) && !is_array($fieldLinks)) // must be null or array
  379. trigger_error('Cannot add parent ('.$parentModule.') of '.get_called_class().', fieldLinks parameter is an invalid type.',E_USER_ERROR);
  380. $info = array('parentField'=>$parentField, 'fieldLinks' => $fieldLinks, 'text' => $text);
  381. utopia::AddChild($parentModule, get_called_class(), $info);
  382. return $fieldLinks;
  383. }
  384. public static function AddChild($childModule,$fieldLinks=NULL,$parentField=NULL,$text=NULL) {
  385. //$childModule = (string)$childModule;
  386. //echo "addchild $childModule<br/>";
  387. //$obj = utopia::GetInstance($childModule);
  388. $childModule::AddParent(get_called_class(),$fieldLinks,$parentField,$text);
  389. }
  390. /**
  391. * Register this module to receive Ajax calls. Normal execution will be terminated in order to process the specified callback function.
  392. *
  393. * @param string $ajaxIdent
  394. * @param string $callback
  395. * @param (bool|function) $requireLogin Either True or False for admin login, or a custom callback function.
  396. * @return bool
  397. */
  398. public function RegisterAjax($ajaxIdent, $callback) {
  399. return utopia::RegisterAjax($ajaxIdent, $callback);
  400. }
  401. public function GetVar($varname) {
  402. return $this->$varname;
  403. }
  404. final static public function GetUUID() {
  405. $class = get_called_class();
  406. if (isset($class::$uuid)) return $class::$uuid;
  407. $uuid = preg_replace('((.{8})(.{4})(.{4})(.{4})(.+))','$1-$2-$3-$4-$5',md5($class));
  408. return $uuid;
  409. }
  410. public function GetModuleId() {
  411. return utopia::GetModuleId($this);
  412. }
  413. public $rewriteMapping=NULL;
  414. public $rewriteURLReadable=NULL;
  415. public $rewritePersistPath=FALSE;
  416. public function HasRewrite($field = NULL) {
  417. if ($this->rewriteMapping === NULL) return false;
  418. if ($field === NULL) return $this->rewriteMapping !== NULL;
  419. foreach ($this->rewriteMapping as $key => $map) {
  420. if (strpos($map,'{'.$field.'}') !== FALSE) return true;
  421. }
  422. return false;
  423. }
  424. /**
  425. * Indicate that this module should rewrite its URL
  426. * @param mixed $mapping NULL to turn off rewriting. FALSE to strip all but uuid. TRUE to allow longer path. ARRAY( '{fieldName}' , 'some-random-text' , '{another_field}' )
  427. * @param bool $URLReadable specifies that all segments of the url should be stripped of non-alphanumeric characters.
  428. */
  429. public function SetRewrite($mapping,$URLReadable = false) {
  430. if ($mapping === NULL) {
  431. $this->rewriteMapping = NULL; return;
  432. }
  433. if (is_string($mapping) && $mapping !== '') $mapping = array($mapping);
  434. if ($mapping === true) $this->rewritePersistPath = true;
  435. if (!is_array($mapping)) $mapping = array();
  436. // defaults?
  437. $this->rewriteMapping = $mapping;
  438. $this->rewriteURLReadable = $URLReadable;
  439. $this->ParseRewrite();
  440. }
  441. public function ParseRewrite($caseSensative = false) {
  442. if ($this->rewriteMapping === NULL) return FALSE;
  443. if (get_class($this) !== utopia::GetCurrentModule()) return FALSE;
  444. $uuid = $this->GetUUID(); if (is_array($uuid)) $uuid = reset($uuid);
  445. $sections = utopia::GetRewriteURL();
  446. $sections = preg_replace('/^'.preg_quote($uuid,'/').'\/?/','',$sections);
  447. $sections = explode('/',$sections);
  448. if (!$sections) return FALSE;
  449. $return = array('uuid'=>$uuid);
  450. foreach ($sections as $key => $value) {
  451. $replace = array();
  452. if (!array_key_exists($key,$this->rewriteMapping)) continue;
  453. $map = $this->rewriteMapping[$key];
  454. // generate preg for section
  455. if (preg_match_all('/{([a-zA-Z0-9_]+)}/',$map,$matches)) {
  456. foreach ($matches[1] as $match) {
  457. $map = str_replace('{'.$match.'}','(.*)',$map);
  458. $replace[] = $match;
  459. }
  460. }
  461. if (preg_match('/'.$map.'/',$value,$matches)) {
  462. unset($matches[0]);
  463. foreach($matches as $key => $match) {
  464. $return[$replace[$key-1]] = $match;
  465. }
  466. }
  467. }
  468. // TODO: named filters not being picked up
  469. $_GET = array_merge($return,$_GET);
  470. $_REQUEST = array_merge($return,$_REQUEST);
  471. return $return;
  472. }
  473. public function RewriteURL(&$filters) {
  474. $mapped = $this->rewriteMapping;
  475. foreach ($mapped as $key => $val) {
  476. if (preg_match_all('/{([a-zA-Z0-9_]+)}/',$val,$matches)) {
  477. $URLreadable = is_array($this->rewriteURLReadable) ? $this->rewriteURLReadable[$key] : $this->rewriteURLReadable;
  478. foreach ($matches[1] as $fieldName) {
  479. $newVal = '';
  480. if (array_key_exists($fieldName,$filters)) $newVal = $filters[$fieldName];
  481. elseif (array_key_exists('_f_'.$fieldName,$filters)) $newVal = $filters['_f_'.$fieldName];
  482. unset($filters[$fieldName]);
  483. unset($filters['_f_'.$fieldName]);
  484. if ($URLreadable) $newVal = trim(UrlReadable($newVal),'-');
  485. $mapped[$key] = str_replace('{'.$fieldName.'}',$newVal,$mapped[$key]);
  486. }
  487. }
  488. if ($mapped[$key] === preg_replace('/{([a-zA-Z0-9_]+)}/','',$val)) $mapped[$key] = '';
  489. $mapped[$key] = rawurlencode($mapped[$key]);
  490. }
  491. if (isset($filters['uuid'])) unset($filters['uuid']);
  492. $uuid = $this->GetUUID(); if (is_array($uuid)) $uuid = reset($uuid);
  493. array_unshift($mapped,$uuid);
  494. $newPath = PATH_REL_ROOT.join('/',$mapped);
  495. $oldPath = parse_url($_SERVER['REQUEST_URI'],PHP_URL_PATH);
  496. if (strpos($oldPath,'/u/')===0) $oldPath = str_replace('/u/','/',$oldPath);
  497. if ($this->rewritePersistPath && utopia::GetCurrentModule() == get_class($this)) $newPath .= str_replace($newPath,'',$oldPath);
  498. // DONE: ensure all rewrite segments are accounted for (all '/' are present)
  499. return rtrim($newPath,'/');
  500. }
  501. public function GetURL($filters = NULL) {
  502. if (!is_array($filters)) $filters = array();
  503. $uuid = $this->GetUUID(); if (is_array($uuid)) $uuid = reset($uuid);
  504. unset($filters['uuid']);
  505. $filters = array('uuid'=>$uuid) + $filters;
  506. $url = DEFAULT_FILE;
  507. if ($this->rewriteMapping !== NULL)
  508. $url = $this->RewriteURL($filters);
  509. $query = http_build_query($filters);
  510. if ($query) $query = '?'.$query;
  511. return $url.$query;
  512. }
  513. public function IsInstalled() {
  514. return utopia::ModuleExists(get_class($this));
  515. }
  516. public function GetSortOrder() {
  517. //if (is_object($module)) $module = get_class($module);
  518. // if (get_class($this) == utopia::GetCurrentModule()) return 1;
  519. return NULL;
  520. }
  521. public function __construct() { if (!defined('INIT_COMPLETE')) throw new Exception('No instances should be created until init is complete'); $this->_SetupParents(); }
  522. public abstract function RunModule(); // called when current_path = parent_path/<module_name>/
  523. }
  524. /**
  525. * Abstract class extending the basic module, adding data access and filtering.
  526. *
  527. */
  528. abstract class uDataModule extends uBasicModule {
  529. public function __construct() {
  530. parent::__construct();
  531. $this->_SetupFields();
  532. $parents = utopia::GetParents(get_class($this));
  533. foreach ($parents as $parent => $pArr) {
  534. foreach ($pArr as $info) {
  535. if (isset($info['fieldLinks'])) foreach ($info['fieldLinks'] as $link) {
  536. $this->AddFilter($link['toField'],ctEQ,itNONE);
  537. }
  538. }
  539. }
  540. }
  541. public $fields = array();
  542. public $filters = array(FILTER_WHERE=>array(),FILTER_HAVING=>array());
  543. public $sqlTableSetupFlat = NULL;
  544. public $sqlTableSetup = NULL;
  545. public $dataset = NULL;
  546. public $currentRecord = NULL;
  547. public $hasEditableFilters = FALSE;
  548. public function flag_is_set($flag,$field=null) {
  549. if ($field && isset($this->fields[$field]['options'])) return flag_is_set($this->fields[$field]['options'],$flag);
  550. return parent::flag_is_set($flag);
  551. }
  552. public abstract function GetTabledef();
  553. public abstract function SetupFields();
  554. public $isAjax = true;
  555. public $fieldsSetup = FALSE;
  556. public function _SetupFields() {
  557. if ($this->fieldsSetup == TRUE) return;
  558. $this->fieldsSetup = TRUE;
  559. $fltr =& $this->AddFilter(array($this,'GetGlobalSearch'),ctCUSTOM,'search',null,null,'Search');
  560. $fltr['uid'] = $this->GetModuleId().'_global_search_';
  561. $fltr['attributes']['class'] = 'uGlobalSearch';
  562. uEvents::TriggerEvent('BeforeSetupFields',$this);
  563. $this->SetupFields();
  564. $this->SetupUnionFields();
  565. if (is_array($this->UnionModules)) foreach ($this->UnionModules as $modulename) {
  566. $obj = utopia::GetInstance($modulename);
  567. $obj->_SetupFields();
  568. }
  569. uEvents::TriggerEvent('AfterSetupFields',$this);
  570. }
  571. public function GetStringFields() {
  572. $ignoreTypes = array(ftIMAGE,ftFILE);
  573. $fields = array();
  574. foreach ($this->sqlTableSetupFlat as $t) {
  575. $o = utopia::GetInstance($t['tModule']);
  576. foreach ($o->fields as $f => $finfo) {
  577. if (in_array($finfo['type'],$ignoreTypes)) continue;
  578. $fields[] = "`{$t['alias']}`.`{$f}`";
  579. }
  580. }
  581. return $fields;
  582. }
  583. public function GetGlobalSearch($val,&$args) {
  584. if (!$val) return '';
  585. $all = array(array());
  586. $cAll = count($all);
  587. $fields = $this->GetStringFields();
  588. // match phrases
  589. preg_match_all('/(".+?")|([\w\+\']+)/',$val,$matches);
  590. foreach ($matches[0] as $v) {
  591. $v = trim($v,'"');
  592. switch (strtolower($v)) {
  593. case 'or': $all[] = array(); $cAll = count($all);
  594. case 'and': continue 2;
  595. }
  596. $allflds = array();
  597. foreach ($fields as $f) {
  598. $args[] = '%'.$v.'%';
  599. $allflds[] = $f.' LIKE ?';
  600. }
  601. $all[$cAll-1][] = '('.implode(' OR ',$allflds).')'.PHP_EOL;
  602. }
  603. $a = array();
  604. foreach ($all as $or) {
  605. $a[] = implode(' AND ',$or);
  606. }
  607. return implode(' OR ',$a);
  608. }
  609. public function ParseRewrite($caseSensative = false) {
  610. $this->_SetupFields();
  611. $parsed = parent::ParseRewrite($caseSensative);
  612. if (!$parsed) return FALSE;
  613. foreach ($parsed as $key => $val) {
  614. // check for filter with key
  615. $filter =& $this->FindFilter($key);
  616. if ($filter) $filter['value'] = $val;
  617. }
  618. return $parsed;
  619. }
  620. /* public function RewriteURL($filters) {
  621. foreach ($filters as $key => $val) {
  622. $fltr =& $this->FindFilter($key);
  623. if ($fltr) {
  624. $filters['_f_'.$fltr['uid']] = $val;
  625. unset($filters[$key]);
  626. }
  627. }
  628. //print_r($filters);
  629. return parent::RewriteURL($filters);
  630. }*/
  631. public function MergeRewriteFilters(&$filters,$rec) {
  632. if (!is_array($filters)) return false;
  633. if (!$this->HasRewrite()) return false;
  634. if (!$rec) return false;
  635. foreach ($this->rewriteMapping as $seg) {
  636. if (preg_match_all('/{([a-zA-Z0-9_]+)}/',$seg,$matches)) {
  637. foreach ($matches[1] as $match) {
  638. if (isset($filters[$match])) continue;
  639. if (array_key_exists($match,$rec)) $filters[$match] = $rec[$match];
  640. $fltr = $this->FindFilter($match);
  641. if (!$fltr) continue;
  642. unset($filters['_f_'.$fltr['uid']]);
  643. }
  644. }
  645. }
  646. return true;
  647. }
  648. public function RewriteFilters(&$filters = NULL) {
  649. if (!is_array($filters)) return false;
  650. if (!$this->HasRewrite()) return false;
  651. foreach ($filters as $uid => $val) {
  652. $fltr = $this->GetFilterInfo(substr($uid,3));
  653. if (!$fltr) continue;
  654. if ($fltr['default'] == $fltr['value']) continue;
  655. $filters[$fltr['fieldName']] = $val;
  656. unset($filters[$uid]);
  657. }
  658. if (array_key_exists($this->GetPrimaryKey(), $filters)) {
  659. foreach ($this->rewriteMapping as $seg) {
  660. if (preg_match_all('/{([a-zA-Z0-9_]+)}/',$seg,$matches)) {
  661. foreach ($matches[1] as $match) {
  662. if (isset($filters[$match])) continue;
  663. $rec = $this->LookupRecord($filters[$this->GetPrimaryKey()],true);
  664. if (!$rec) return;
  665. $this->MergeRewriteFilters($filters,$rec);
  666. return;
  667. }
  668. }
  669. }
  670. }
  671. return true;
  672. }
  673. public function GetURL($filters = NULL) {
  674. $this->_SetupParents();
  675. $this->_SetupFields();
  676. if ($filters === FALSE) return parent::GetURL($filters);
  677. if (!is_array($filters) && $filters !== NULL) {
  678. $f = $this->FindFilter($this->GetPrimaryKey());
  679. if ($f) $filters = array('_f_'.$f['uid']=>$filters);
  680. else $filters = array($this->GetPrimaryKey()=>$filters);
  681. }
  682. foreach ($this->filters as $filterType) {
  683. foreach ($filterType as $filterSet) {
  684. foreach ($filterSet as $filter) {
  685. // is the current filter referenced in $filters? if not, continue;
  686. if (!is_callable($filter['fieldName']) && !isset($filters[$filter['fieldName']]) && !isset($filters['_f_'.$filter['uid']])) continue;
  687. $val = $this->GetFilterValue($filter['uid']);
  688. if (!is_callable($filter['fieldName']) && isset($filters[$filter['fieldName']])) $val = $filters[$filter['fieldName']];
  689. if (isset($filters['_f_'.$filter['uid']])) $val = $filters['_f_'.$filter['uid']];
  690. /*if (!empty($filter['default']) && $val == $filter['default']) {
  691. unset($filters[$filter['fieldName']]);
  692. unset($filters['_f_'.$filter['uid']]);
  693. continue;
  694. }*/
  695. if (!$val) continue;
  696. if ($this->HasRewrite($filter['fieldName'])) {
  697. if (isset($filters[$filter['fieldName']])) continue;
  698. $filters[$filter['fieldName']] = $val;
  699. unset($filters['_f_'.$filter['uid']]);
  700. continue;
  701. }
  702. continue; // skip below
  703. $filters['_f_'.$filter['uid']] = $val;
  704. unset($filters[$filter['fieldName']]);
  705. }
  706. }
  707. }
  708. $this->RewriteFilters($filters);
  709. return parent::GetURL($filters);
  710. }
  711. public function GetEncodedFieldName($field,$pkValue=NULL) {
  712. $pk = is_null($pkValue) ? '' : "($pkValue)";
  713. return cbase64_encode(get_class($this).":$field$pk");
  714. }
  715. public function CreateSqlField($field,$pkValue) {
  716. return "usql-".$this->GetEncodedFieldName($field,$pkValue);
  717. }
  718. public function GetDeleteButton($pk,$btnText = NULL,$title = NULL) {
  719. $title = $title ? "Delete '$title'" : 'Delete Record';
  720. return '<a class="btn btn-del" name="'.$this->CreateSqlField('__u_delete_record__',$pk).'" href="#" title="'.$title.'">'.$btnText.'</a>';
  721. }
  722. public function DrawSqlInput($field,$defaultValue='',$pkValue=NULL,$attributes=NULL,$inputTypeOverride=NULL,$valuesOverride=NULL) {
  723. $of = $field;
  724. if (strpos($field,':') !== FALSE) list($field) = explode(':',$field);
  725. if ($attributes==NULL) $attributes = array();
  726. if (isset($this->fields[$field]['attr'])) $attributes = array_merge($this->fields[$field]['attr'],$attributes);
  727. $inputType = $inputTypeOverride ? $inputTypeOverride : $this->fields[$field]['inputtype'];
  728. $length = $this->GetFieldProperty($field,'length') ? $this->GetFieldProperty($field,'length') : $this->GetTableProperty($field,'length');
  729. $values = $valuesOverride ? $valuesOverride : $this->GetValues($field,$pkValue);
  730. if (isset($this->fields[$field]['vtable']['parent']) && !is_subclass_of($this->fields[$field]['vtable']['tModule'],'iLinkTable') && $pkValue !== NULL) {
  731. $rec = null;
  732. foreach ($this->fields[$field]['vtable']['joins'] as $from=>$to) {
  733. if ($from == $field) {
  734. if (!$rec) $rec = $this->LookupRecord($pkValue);
  735. $defaultValue = $rec[$this->GetPrimaryKeyConstant($field)];
  736. break;
  737. }
  738. }
  739. }
  740. if (!isset($attributes['class'])) $attributes['class'] = '';
  741. if ($this->isAjax) $attributes['class'] .= ' uf';
  742. $attributes['class'] = trim($attributes['class']);
  743. $fieldName = $this->CreateSqlField($of,$pkValue);
  744. if ($inputType == itFILE) $attributes['id'] = $fieldName;
  745. return utopia::DrawInput($fieldName,$inputType,$defaultValue,$values,$attributes);
  746. }
  747. public function GetPrimaryKeyConstant($fieldAlias=NULL) {
  748. if (!is_null($fieldAlias)) {
  749. return '_'.$this->GetFieldProperty($fieldAlias,'tablename').'_pk';
  750. }
  751. return '_'.$this->sqlTableSetup['alias'].'_pk';
  752. }
  753. public function GetPrimaryKey() {
  754. return $this->sqlTableSetup['pk'];
  755. }
  756. public $pkt = NULL;
  757. public function GetPrimaryKeyTable($fieldAlias=NULL) {
  758. if (!is_null($fieldAlias)) {
  759. $setup = $this->sqlTableSetupFlat[$this->GetFieldProperty($fieldAlias,'tablename')];
  760. return $setup['pk'];
  761. }
  762. return $this->sqlTableSetup['pk'];
  763. }
  764. public $pt = NULL;
  765. public function GetPrimaryTable($fieldAlias=NULL) {
  766. if (!is_null($fieldAlias)) {
  767. $setup = $this->sqlTableSetupFlat[$this->GetFieldProperty($fieldAlias,'tablename')];
  768. return $setup['table'];
  769. }
  770. if ($this->pt == NULL) $this->pt = TABLE_PREFIX.$this->GetTabledef();
  771. return $this->pt;
  772. }
  773. public $UnionModules = NULL;
  774. public $UNION_MODULE = FALSE;
  775. public function AddUnionModule($modulename) {
  776. // check fields match! Union modules MUST have identical fields
  777. $this->UNION_MODULE = TRUE;
  778. if ($this->UnionModules == NULL) {
  779. $this->UnionModules = array();
  780. // $this->_SetupFields();
  781. // $this->AddField('__module__',"'".get_class($this)."'",'');
  782. // $tbl = is_array($this->sqlTableSetup) ? $this->sqlTableSetup['alias'] : '';
  783. // $this->AddField('__module_pk__',$this->GetPrimaryKey(),$tbl);
  784. }
  785. $this->UnionModules[] = $modulename;
  786. $obj = utopia::GetInstance($modulename);
  787. $obj->UNION_MODULE = TRUE;
  788. }
  789. public function AddUnionParent($parentModule) {
  790. $obj = utopia::GetInstance($parentModule);
  791. $obj->AddUnionModule(get_class($this));
  792. }
  793. public function SetupUnionFields() {
  794. if ($this->UNION_MODULE !== TRUE) return;
  795. $this->AddField('__module__',"'".get_class($this)."'",'');
  796. $tbl = is_array($this->sqlTableSetup) ? $this->sqlTableSetup['alias'] : '';
  797. $this->AddField('__module_pk__',$this->GetPrimaryKey(),$tbl);
  798. }
  799. // this value will be used on new records or field updates
  800. public function SetDefaultValue($name,$moduleOrValue,$getField=NULL,$valField=NULL,$onlyIfNull=FALSE) { //,$whereField=NULL,$rowField=NULL) {
  801. if ($getField==NULL || $valField==NULL) {
  802. $this->SetFieldProperty($name,'default_value',$moduleOrValue);
  803. } else {
  804. $this->SetFieldProperty($name,'default_lookup',array('module'=>$moduleOrValue,'getField'=>$getField,'valField'=>$valField));
  805. // create a callback, when valField is updated, to set value of $name to the new DefaultValue (IF that value is empty?)
  806. if (!array_key_exists($valField,$this->fields) && get_class($this) != utopia::GetCurrentModule() && utopia::GetCurrentModule()) {
  807. $obj = utopia::GetInstance(utopia::GetCurrentModule());
  808. $obj->AddOnUpdateCallback($valField,array($this,'RefreshDefaultValue'),$name,$onlyIfNull);
  809. } else
  810. $this->AddOnUpdateCallback($valField,array($this,'RefreshDefaultValue'),$name,$onlyIfNull);
  811. }
  812. }
  813. protected function RefreshDefaultValue($pkVal, $fieldName,$onlyIfNull) {
  814. $row = $this->LookupRecord($pkVal);
  815. if ($row == NULL) return;
  816. if ($onlyIfNull && !empty($row[$fieldName])) return;
  817. //$pkVal = $row[$this->GetPrimaryKey()];
  818. $dVal = $this->GetDefaultValue($fieldName);
  819. // echo "update $fieldName on $pkVal to: $dVal";
  820. $this->UpdateField($fieldName,$dVal,$pkVal);
  821. }
  822. public function GetDefaultValue($name) {
  823. // echo "GetDefaultValue($name)";
  824. // default value should take implicit value as priority over filter value?
  825. // NO implicit value takes priority
  826. // is d_v set?
  827. $dv = $this->GetFieldProperty($name,'default_value');
  828. if (!empty($dv)) return $dv;
  829. // is d_l set? lookup value... lookup field from module,,, what about WHERE?
  830. $dl = $this->GetFieldProperty($name,'default_lookup');
  831. if (!empty($dl)) {
  832. // find the value of valField
  833. $row = $this->GetCurrentRecord();
  834. return $row[$dl['getField']];
  835. }
  836. // is filter set?
  837. //print_r($this->filters);
  838. //AjaxEcho("//no predifined default set, searching filters");
  839. $fltr =& $this->FindFilter($name,ctEQ);
  840. if ($fltr === NULL) return '';
  841. $uid = $fltr['uid'];
  842. $value = $this->GetFilterValue($uid);
  843. if (!empty($value)) return $value;
  844. return '';
  845. // not processed
  846. /* foreach ($this->filters[FILTER_WHERE] as $filterset) {
  847. if (!array_key_exists($name,$filterset))continue;
  848. $filterData = $filterset[$name];
  849. if (empty($filterData)) continue;
  850. if ($filterData['ct'] != ctEQ) continue;
  851. return $filterData['value'];
  852. }
  853. foreach ($this->filters[FILTER_HAVING] as $filterset) {
  854. if (!array_key_exists($name,$filterset))continue;
  855. $filterData = $filterset[$name];
  856. if (empty($filterData)) continue;
  857. if ($filterData['ct'] != ctEQ) continue;
  858. return $filterData['value'];
  859. }*/
  860. }
  861. public function GetLookupValue($alias,$pkValue) {
  862. if (empty($pkValue)) return;
  863. $this->_SetupFields();
  864. $fieldData = $this->fields[$alias];
  865. $str = $this->GetFieldLookupString($alias)." as `$alias`";
  866. $table = $fieldData['vtable']['table'];
  867. $pk = $fieldData['vtable']['pk'];
  868. $stm = database::query("SELECT $str FROM $table as {$fieldData['tablename']} WHERE $pk = ?",array($pkValue));
  869. $row = $stm->fetch();
  870. if (empty($row[$alias])) return $pkValue;
  871. return $row[$alias];
  872. }
  873. public function TableExists($alias) {
  874. return array_key_exists($alias,$this->sqlTableSetupFlat);
  875. }
  876. /**
  877. * Creates a virtual table for use within the module. Allowing access to fields within the database.
  878. * @param string $alias table alias
  879. * @param string $tableModule classname of the uTableDef
  880. * @param string $parent alias of the table to join to
  881. * @param mixed $joins string if fields to join have the same name, array to specify different fields array('parent_field'=>'local_field')
  882. * @param string $joinType specify the type of join to perform (default: LEFT JOIN)
  883. * @see AddField
  884. */
  885. public function CreateTable($alias, $tableModule=NULL, $parent=NULL, $joins=NULL, $joinType='LEFT JOIN') {
  886. // nested array
  887. // first create the current alias
  888. if ($tableModule == NULL) $tableModule = $this->GetTabledef();
  889. // $alias = strtolower($alias);
  890. // $parent = strtolower($parent);
  891. // $fromField = strtolower($fromField);
  892. // $toField = strtolower($toField);
  893. if (!$this->sqlTableSetupFlat) $this->sqlTableSetupFlat = array();
  894. if ($this->TableExists($alias)) { throw new Exception("Table with alias '$alias' already exists"); return; }
  895. $tableObj = utopia::GetInstance($tableModule);
  896. $newTable = array();
  897. $this->sqlTableSetupFlat[$alias] =& $newTable;
  898. $newTable['alias'] = $alias;
  899. $newTable['table'] = TABLE_PREFIX.$tableModule;
  900. $newTable['pk'] = $tableObj->GetPrimaryKey();
  901. $newTable['tModule']= $tableModule;
  902. $this->AddField('_'.$alias.'_pk',$newTable['pk'],$alias);
  903. //$this->AddFilter('_'.$alias.'_pk',ctEQ,itNONE);
  904. if ($parent==NULL) {
  905. if ($this->sqlTableSetup != NULL) {
  906. ErrorLog('Can only have one base table');
  907. return;
  908. }
  909. $this->sqlTableSetup = $newTable;
  910. $this->AddField($newTable['pk'],$newTable['pk'],$alias);
  911. $this->AddFilter($newTable['pk'],ctEQ,itNONE);
  912. $this->AddField('_module',"'".get_class($this)."'",$alias);
  913. return;
  914. } else {
  915. $newTable['parent'] = $parent;
  916. }
  917. // $fromField in $this->sqlTableSetupFlat[$parent]['tModule']
  918. if (is_string($joins)) $joins = array($joins=>$joins);
  919. if (is_array($joins)) {
  920. $fromTable = utopia::GetInstance($this->sqlTableSetupFlat[$parent]['tModule']);
  921. $toTable = utopia::GetInstance($tableModule);
  922. foreach ($joins as $fromField => $toField) {
  923. if (!$fromTable->IsIndex($fromField)) error_log("Field `$fromField` used as lookup but NOT an indexed field in table `".$this->sqlTableSetupFlat[$parent]['tModule'].'`.');
  924. if (!$toTable->IsIndex($toField)) error_log("Field `$toField` used as lookup but NOT an indexed field in table `".$tableModule.'`.');
  925. }
  926. }
  927. //$newTable['fromField'] = $fromField;
  928. //$newTable['toField'] = $toField;
  929. $newTable['parent'] = $parent;
  930. $newTable['joins'] = $joins;
  931. $newTable['joinType'] = $joinType;
  932. // $this->sqlTableSetupFlat[$alias] = $newTable;
  933. // search through the table setup looking for the $linkFrom alias
  934. if (($srchParent =& $this->recurseSqlSetupSearch($this->sqlTableSetup,$parent))) {
  935. // found, add it
  936. if (!array_key_exists('children',$srchParent)) $srchParent['children'] = array();
  937. $srchParent['children'][] = $newTable;
  938. } else {
  939. // not found.. throw error
  940. ErrorLog("Cannot find $parent");
  941. }
  942. if (is_subclass_of($tableModule,'iLinkTable')) {
  943. $this->AddGrouping($this->GetPrimaryKey());
  944. }
  945. }
  946. public function GetValues($alias,$pkVal=null,$stringify = FALSE) {
  947. if (!is_string($alias)) return;
  948. $values = null;
  949. if (!isset($this->fields[$alias])) {
  950. $fltr =& $this->FindFilter($alias);
  951. $values = $fltr['values'];
  952. if (!is_array($values) && is_string($fltr['fieldName']) && isset($this->fields[$fltr['fieldName']])) $values = $this->GetValues($fltr['fieldName'],$pkVal,$stringify);
  953. } else {
  954. $values = $this->fields[$alias]['values'];
  955. }
  956. if (is_callable($values)) return call_user_func_array($values,array($this,$alias,$pkVal));
  957. if (!is_array($values) && IsSelectStatement($values)) {
  958. $values = database::getKeyValuePairs($values);
  959. }
  960. if ($stringify && is_array($values) && $values) {
  961. $values = array_combine(array_values($values),array_values($values));
  962. }
  963. return $values;
  964. }
  965. public function SetFieldOptions($alias,$newoptions) {
  966. $this->SetFieldProperty($alias,'options',$newoptions);
  967. }
  968. protected $spacerCount = NULL;
  969. public function AddSpacer($text = '&nbsp;',$titleText = '') {
  970. if ($this->spacerCount === NULL) $this->spacerCount = 0;
  971. $this->AddField("__spacer_{$this->spacerCount}__","'$text'",'',"$titleText");
  972. $this->spacerCount = $this->spacerCount + 1;
  973. }
  974. public function CharAlign($alias,$char) {
  975. $this->fields[$alias]['charalign'] = $char;
  976. }
  977. public function SetFormat($alias,$format,$condition = TRUE) {
  978. $this->fields[$alias]['formatting'][] = array('format' =>$format, 'condition' => $condition);
  979. }
  980. public $layoutSections = array();
  981. public $cLayoutSection = null;
  982. public function NewSection($title,$order = null) {
  983. if ($order === null) $order = count($this->layoutSections);
  984. $a = array('title'=>$title,'order'=>$order);
  985. $found = false;
  986. foreach ($this->layoutSections as $k => $i) {
  987. if ($i['title'] == $title) {
  988. $this->layoutSections[$k] = $a; $found = $k; break;
  989. }
  990. }
  991. $this->cLayoutSection = $found;
  992. if ($found === FALSE) {
  993. $this->layoutSections[] = $a;
  994. $this->cLayoutSection = count($this->layoutSections) -1;
  995. }
  996. array_sort_subkey($this->layoutSections,'order');
  997. }
  998. public function RenameSection($old,$new) {
  999. foreach ($this->layoutSections as $k => $i) {
  1000. if ($i['title'] == $old) {
  1001. $this->layoutSections[$k]['title'] = $new; break;
  1002. }
  1003. }
  1004. }
  1005. protected $defaultStyles = array();
  1006. public function FieldStyles_SetDefault($inputType,$style) {
  1007. if (!is_array($style)) { ErrorLog("Field Style is not an array ($field)"); return; }
  1008. $this->defaultStyles[$inputType] = $style;
  1009. }
  1010. public function FieldStyles_Set($field,$style) {
  1011. if (!is_array($style)) { ErrorLog("Field Style is not an array ($field)"); return; }
  1012. $this->fields[$field]['style'] = $style;
  1013. }
  1014. public function FieldStyles_Add($field,$style) {
  1015. if (!is_array($style)) { ErrorLog("Field Style is not an array ($field)"); return; }
  1016. foreach ($style as $key=>$val)
  1017. $this->fields[$field]['style'][$key] = $val;
  1018. }
  1019. public function FieldStyles_Unset($field,$key) {
  1020. unset($this->fields[$field]['style'][$key]);
  1021. }
  1022. public function FieldStyles_Get($field,$rec=NULL) {
  1023. if (strpos($field,':') !== FALSE) list($field) = explode(':',$field);
  1024. if (!isset($this->fields[$field])) return null;
  1025. $inputType = $this->fields[$field]['inputtype'];
  1026. $defaultStyles = array_key_exists($inputType,$this->defaultStyles) ? $this->defaultStyles[$inputType] : array();
  1027. $specificStyles = $this->GetFieldProperty($field,'style'); if (!$specificStyles) $specificStyles = array();
  1028. $conditionalStyles = array();
  1029. if (isset($this->fields[$field]['style_fn']) && is_callable($this->fields[$field]['style_fn'][0])) {
  1030. $args = $this->fields[$field]['style_fn'][1];
  1031. if (is_array($args)) array_unshift($args,$rec); else $args = array($rec);
  1032. array_unshift($args,$field);
  1033. $conditionalStyles = call_user_func_array($this->fields[$field]['style_fn'][0],$args);
  1034. }
  1035. if (!$conditionalStyles) $conditionalStyles = array();
  1036. $styles = array_merge($defaultStyles,$specificStyles,$conditionalStyles);
  1037. // if width/height has no delimiter, append 'px'
  1038. if (isset($styles['width']) && is_numeric($styles['width'])) $styles['width'] = $styles['width'].'px';
  1039. if (isset($styles['height']) && is_numeric($styles['height'])) $styles['height'] = $styles['height'].'px';
  1040. return $styles;
  1041. }
  1042. public function ConditionalStyle_Set($field,$callback,$args=null) {
  1043. $this->fields[$field]['style_fn'] = array($callback,$args);
  1044. }
  1045. public function ConditionalStyle_Unset($field) {
  1046. unset($this->fields[$field]['style_fn']);
  1047. }
  1048. /**
  1049. * Defines a virtual field in the module, allowing access to the data within the database.
  1050. * @see CreateTable
  1051. */
  1052. public function &AddField($aliasName,$fieldName,$tableAlias=NULL,$visiblename=NULL,$inputtype=itNONE,$values=NULL) {//,$options=0,$values=NULL) {
  1053. $this->_SetupFields();
  1054. if ($tableAlias === NULL) $tableAlias = $this->sqlTableSetup['alias'];
  1055. $this->fields[$aliasName] = array(
  1056. 'alias' => $aliasName,
  1057. 'tablename' => $tableAlias,
  1058. 'visiblename' => $visiblename,
  1059. 'inputtype' => $inputtype,
  1060. 'options' => NULL, // this can be re-set using $this->SetFieldOptions
  1061. 'field' => $fieldName,
  1062. );
  1063. $this->fields[$aliasName]['order'] = count($this->fields);
  1064. $before = $this->insertBefore;
  1065. if (is_string($before) && isset($this->fields[$before])) {
  1066. $this->fields[$aliasName]['order'] = $this->fields[$before]['order'] - 0.5;
  1067. }
  1068. if (is_array($fieldName)) {
  1069. $this->fields[$aliasName]['field'] = "";
  1070. $this->AddPreProcessCallback($aliasName, $fieldName);
  1071. }
  1072. if ($tableAlias) $this->fields[$aliasName]['vtable'] = $this->sqlTableSetupFlat[$tableAlias];
  1073. switch ($this->GetFieldType($aliasName)) {
  1074. case ftFILE:
  1075. case ftIMAGE:
  1076. case ftUPLOAD:
  1077. $this->AddField($aliasName.'_filename', $fieldName.'_filename', $tableAlias);
  1078. $this->AddField($aliasName.'_filetype', $fieldName.'_filetype', $tableAlias);
  1079. break;
  1080. case ftCURRENCY:
  1081. $list = uLocale::ListLocale();
  1082. $this->AddField($aliasName.'_locale', $fieldName.'_locale', $tableAlias,(count($list)>1 ? 'Currency' : NULL),itCOMBO,$list);
  1083. $this->AddPreProcessCallback($aliasName,array('utopia','convCurrency'));
  1084. break;
  1085. case ftDATE:
  1086. $this->AddPreProcessCallback($aliasName,array('utopia','convDate'));
  1087. break;
  1088. case ftTIME:
  1089. $this->AddPreProcessCallback($aliasName,array('utopia','convTime'));
  1090. break;
  1091. case ftDATETIME:
  1092. case ftTIMESTAMP:
  1093. $this->AddPreProcessCallback($aliasName,array('utopia','convDateTime'));
  1094. break;
  1095. }
  1096. // values here
  1097. if ($values === NULL) switch ($inputtype) {
  1098. //case itNONE: // commented to prevent huge memory usage on BLOB fields. Set Values to true if you need it!
  1099. case itCOMBO:
  1100. case itOPTION:
  1101. $values = true;
  1102. default:
  1103. break;
  1104. }
  1105. $this->fields[$aliasName]['values'] = $values;
  1106. if ($visiblename !== NULL) {
  1107. if (!$this->layoutSections) $this->NewSection('');
  1108. $this->fields[$aliasName]['layoutsection'] = $this->cLayoutSection;
  1109. }
  1110. return $this->fields[$aliasName];
  1111. }
  1112. protected $insertBefore = null;
  1113. public function SetAddFieldPosition($before=null) {
  1114. $this->insertBefore = $before;
  1115. }
  1116. public function GetFields($visibleOnly=false,$layoutSection=NULL) {
  1117. $arr = $this->fields;
  1118. array_sort_subkey($arr,'order');
  1119. foreach ($arr as $fieldName=>$fieldInfo) {
  1120. if ($visibleOnly && $fieldInfo['visiblename']===NULL) unset($arr[$fieldName]);
  1121. if ($layoutSection !== NULL && array_key_exists('layoutsection',$fieldInfo) && $fieldInfo['layoutsection'] !== $layoutSection) unset($arr[$fieldName]);
  1122. }
  1123. return $arr;
  1124. }
  1125. public function AssertField($field,$reference = NULL) {
  1126. if (isset($this->fields[$field])) return TRUE;
  1127. if (IsSelectStatement($field)) return FALSE;
  1128. $found = useful_backtrace(0);
  1129. ErrorLog("Field ($field) does not exist for {$found[0][0]}:{$found[0][1]} called by {$found[1][0]}:{$found[1][1]}.".($reference ? " Ref: $reference" : ''));
  1130. }
  1131. public function AddOnUpdateCallback($alias,$callback) {
  1132. $this->_SetupFields();
  1133. if (!$this->AssertField($alias)) return FALSE;
  1134. // echo "add callback ".get_class($this).":$alias";
  1135. $numargs = func_num_args();
  1136. $arr = array();
  1137. for ($i = 2; $i < $numargs; $i++)
  1138. $arr[] = func_get_arg($i);
  1139. $this->fields[$alias]['onupdate'][] = array($callback,$arr);
  1140. return TRUE;
  1141. }
  1142. /** Instructs the core to send information to a custom function.
  1143. * Accepts any number of args, passed to the callback function when the PreProcess function is called.
  1144. * -- callback($fieldValue,...)
  1145. * @param string Field Alias to attach the callback to.
  1146. * @param function Reference to the function to call.
  1147. * @param ... additional data
  1148. */
  1149. public function AddPreProcessCallback($alias,$callback) {
  1150. if (!$this->FieldExists($alias)) return FALSE;
  1151. if (count($callback) > 2) {
  1152. $arr = array_splice($callback,2);
  1153. } else {
  1154. $numargs = func_num_args();
  1155. $arr = array();
  1156. for ($i = 2; $i < $numargs; $i++)
  1157. $arr[] = func_get_arg($i);
  1158. }
  1159. $this->fields[$alias]['preprocess'][] = array($callback,$arr);
  1160. }
  1161. public function AddDistinctField($alias) {
  1162. if (!$this->AssertField($alias)) return FALSE;
  1163. // get original field
  1164. $original = $this->fields[$alias];
  1165. $this->AddField("distinct_$alias",'count('.$original['tablename'].'.'.$original['field'].')','',"count($alias)");
  1166. $this->AddFilter("distinct_$alias",ctGTEQ,itNONE,1);
  1167. }
  1168. public $grouping = NULL;
  1169. public function AddGrouping($alias,$clear = false) {
  1170. if (!$this->grouping || $clear) $this->grouping = array();
  1171. if (isset($this->fields[$alias])) $alias = '`'.$this->fields[$alias]['tablename'].'`.`'.$this->fields[$alias]['field'].'`';
  1172. else $alias = '`'.$alias.'`';
  1173. $this->grouping[] = $alias;
  1174. }
  1175. public $ordering = NULL;
  1176. public function AddOrderBy($fieldName,$direction = 'ASC') {
  1177. $fieldName = trim($fieldName);
  1178. if ($this->ordering === NULL) $this->ordering = array();
  1179. if (strpos($fieldName,',') !== FALSE) {
  1180. foreach(explode(',',$fieldName) as $f) {
  1181. $f = trim($f);
  1182. $dir = 'ASC';
  1183. if (strpos($f,' ') !== FALSE) list($f,$dir) = explode(' ',$f);
  1184. $this->AddOrderBy($f,$dir);
  1185. }
  1186. return;
  1187. }
  1188. if (strpos($fieldName,' ') !== FALSE) {
  1189. list($fieldName,$direction) = explode(' ',$fieldName);
  1190. $fieldName = trim($fieldName);
  1191. $direction = trim($direction);
  1192. }
  1193. if ($this->FieldExists($fieldName)) $fieldName = "`$fieldName`";
  1194. $this->ordering[] = "$fieldName $direction";
  1195. }
  1196. /* FILTERS */
  1197. // Filters work on a set/rule basis, each set contains rules which are AND'ed, and each set is then OR'd together this creates full flexibility with filtering the results.
  1198. // EG: (set1.rule1 and set1.rule2) or (set2.rule1)
  1199. // create a new set by calling $this->NewFilterset();
  1200. public function NewFiltersetWhere() {
  1201. $this->filters[FILTER_WHERE][] = array();
  1202. }
  1203. public function NewFiltersetHaving() {
  1204. $this->filters[

Large files files are truncated, but you can click here to view the full file