PageRenderTime 87ms CodeModel.GetById 32ms RepoModel.GetById 1ms 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
  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[FILTER_HAVING][] = array();
  1205. }
  1206. public function ClearFilters() {
  1207. $this->filters = array(FILTER_WHERE=>array(),FILTER_HAVING=>array());
  1208. }
  1209. public function &AddFilterWhere($fieldName,$compareType,$inputType=itNONE,$value=NULL,$values=NULL,$title=NULL) {
  1210. // if (!array_key_exists($fieldName,$this->fields) && $inputType !== itNONE) { ErrorLog("Cannot add editable WHERE filter on field '$fieldName' as the field does not exist."); return; }
  1211. if (!isset($this->filters[FILTER_WHERE]) || count(@$this->filters[FILTER_WHERE]) == 0) $this->NewFiltersetWhere();
  1212. return $this->AddFilter_internal($fieldName,$compareType,$inputType,$value,$values,FILTER_WHERE,$title);
  1213. }
  1214. public function &AddFilter($fieldName,$compareType,$inputType=itNONE,$value=NULL,$values=NULL,$title=NULL) {
  1215. if (is_callable($fieldName)) return $this->AddFilterWhere($fieldName,$compareType,$inputType,$value,$values,$title);
  1216. if (isset($this->fields[$fieldName])) return $this->AddFilterWhere($fieldName,$compareType,$inputType,$value,$values,$title);
  1217. if (preg_match_all('/{([^}]+)}/',$fieldName,$matches)) {
  1218. foreach ($matches[1] as $match) {
  1219. if (isset($this->fields[$match])) return $this->AddFilterWhere($fieldName,$compareType,$inputType,$value,$values,$title);
  1220. }
  1221. }
  1222. if (array_key_exists($fieldName,$this->fields) && stripos($this->fields[$fieldName]['field'],' ') === FALSE && !$this->UNION_MODULE && isset($this->fields[$fieldName]['vtable'])) {
  1223. return $this->AddFilterWhere($this->GetFieldLookupString($fieldName),$compareType,$inputType,$value,$values,$title);
  1224. }
  1225. if ($compareType !== ctIGNORE) error_log('Using HAVING filter in `'.get_class($this).'` on field `'.$fieldName.'`. Please use {field} tags where possible.');
  1226. if (!isset($this->filters[FILTER_HAVING]) || count(@$this->filters[FILTER_HAVING]) == 0) $this->NewFiltersetHaving();
  1227. return $this->AddFilter_internal($fieldName,$compareType,$inputType,$value,$values,FILTER_HAVING,$title);
  1228. }
  1229. protected $filterCount = 0;
  1230. public function GetNewUID($fieldName) {
  1231. $this->filterCount++;
  1232. return $this->GetModuleId().'_'.$this->filterCount;
  1233. }
  1234. // private - must use addfilter or addfilterwhere.
  1235. protected function &AddFilter_internal($fieldName,$compareType,$inputType=itNONE,$dvalue=NULL,$values=NULL,$filterType=NULL,$title=NULL) {
  1236. // if no filter, or filter has default, or filter is link - create new filter
  1237. $fd =& $this->FindFilter($fieldName,$compareType,$inputType);
  1238. if (!$fd || $fd['default'] !== NULL) {
  1239. $uid = $this->GetNewUID($fieldName);
  1240. if ($filterType == NULL) // by default, filters are HAVING unless otherwise specified
  1241. $filterset =& $this->filters[FILTER_HAVING];
  1242. else
  1243. $filterset =& $this->filters[$filterType];
  1244. if ($filterset == NULL) $filterset = array(); // - now manually called NewFilterset####()
  1245. $fieldData = array();
  1246. $fieldData['uid'] = $uid;
  1247. $filterset[count($filterset)-1][] =& $fieldData;
  1248. } else $fieldData =& $fd;
  1249. if ($values === NULL) switch ($inputType) {
  1250. case itNONE:
  1251. case itCOMBO:
  1252. case itOPTION:
  1253. $values = true;
  1254. default:
  1255. break;
  1256. }
  1257. $value = $dvalue;
  1258. // if ($inputType !== itNONE && $value === NULL && array_key_exists($fieldName,$_GET))
  1259. // $value = $_GET[$fieldName];
  1260. $fieldData['fieldName'] = $fieldName;
  1261. $fieldData['type'] = $filterType;
  1262. $fieldData['ct'] = $compareType;
  1263. $fieldData['it'] = $inputType;
  1264. $fieldData['title']= $title;
  1265. $fieldData['values']= $values;
  1266. $fieldData['default'] = $dvalue;
  1267. $fieldData['value'] = $value;
  1268. if ($inputType != itNONE) $this->hasEditableFilters = true;
  1269. return $fieldData;//['uid'];
  1270. }
  1271. // returns false if filter not found, otherwise returns filter information as array
  1272. public function HasFilter($field) {
  1273. if (isset($this->filters[FILTER_HAVING]))
  1274. foreach ($this->filters[FILTER_HAVING] as $filterset) {
  1275. if (isset($filterset[$field])) return $filterset[$field];
  1276. }
  1277. if (isset($this->filters[FILTER_WHERE]))
  1278. foreach ($this->filters[FILTER_WHERE] as $filterset) {
  1279. if (isset($filterset[$field])) return $filterset[$field];
  1280. }
  1281. return false;
  1282. }
  1283. public function FieldExists($fieldName) {
  1284. return isset($this->fields[$fieldName]);
  1285. }
  1286. public function SetFieldProperty($fieldName,$propertyName,$propertyValue) {
  1287. if (!isset($this->fields[$fieldName])) { ErrorLog(get_class($this)."->SetFieldProperty($fieldName,$propertyName). Field does not exist."); return; }
  1288. $this->fields[$fieldName][$propertyName] = $propertyValue;
  1289. }
  1290. public function GetFieldProperty($fieldName,$propertyName) {
  1291. // if (!$this->AssertField($fieldName)) return NULL;
  1292. if (!isset($this->fields[$fieldName])) return NULL;
  1293. if (!isset($this->fields[$fieldName][$propertyName])) return NULL;
  1294. return $this->fields[$fieldName][$propertyName];//[strtolower($propertyName)];
  1295. }
  1296. public function SetFieldType($alias,$type) {
  1297. $this->_SetupFields();
  1298. $ret = $this->GetFieldType($alias);
  1299. $this->SetFieldProperty($alias,'datatype',$type);
  1300. return $ret;
  1301. }
  1302. public function GetFieldType($alias) {
  1303. $type = $this->GetFieldProperty($alias,'datatype');
  1304. if (!$type) $type = $this->GetTableProperty($alias,'type');
  1305. return $type;
  1306. }
  1307. public $hideFilters = FALSE;
  1308. public function HideFilters() {
  1309. $this->hideFilters = TRUE;
  1310. }
  1311. public function GetFieldLookupString($alias) {
  1312. $fieldData = $this->fields[$alias];
  1313. $fieldName = $fieldData['field'];
  1314. if (empty($fieldName)) return "''";
  1315. if ($fieldData['tablename'] === NULL) return;
  1316. /* THIS FUNCTION HAS BEEN SIMPLIFIED!
  1317. * field is ReplacePragma if field has "is_function" property, or starts with "(", single quote, or double quote
  1318. * else, field is CONCAT
  1319. */
  1320. $chr1 = substr($fieldName,0,1);
  1321. if (isset($fieldData['vtable']) && is_subclass_of($fieldData['vtable']['tModule'],'iLinkTable') && $this->sqlTableSetup['alias'] !== $fieldData['tablename']) {
  1322. $toAdd = 'GROUP_CONCAT(DISTINCT `'.$fieldData['tablename'].'`.`'.$fieldName.'` SEPARATOR 0x1F)';
  1323. } elseif (!preg_match('/{[^}]+}/',$fieldData['field'])) {
  1324. if ($chr1 == '(' || $chr1 == "'" || $chr1 == '"')
  1325. $toAdd = $fieldData['field'];
  1326. else
  1327. $toAdd = "`{$fieldData['tablename']}`.`{$fieldData['field']}`";
  1328. } elseif ($this->GetFieldProperty($alias, 'is_function') || $chr1 == '(' || $chr1 == "'" || $chr1 == '"') {
  1329. $toAdd = ReplacePragma($fieldData['field'], $fieldData['tablename']);
  1330. } else {
  1331. $toAdd = CreateConcatString($fieldName, $fieldData['tablename']);
  1332. }
  1333. return "$toAdd";
  1334. }
  1335. public function GetFromClause() {
  1336. $from = "{$this->sqlTableSetup['table']} AS {$this->sqlTableSetup['alias']}";
  1337. $paraCount = $this->parseSqlTableSetupChildren($this->sqlTableSetup,$from);
  1338. // for ($i = 0; $i < $paraCount; $i++)
  1339. // $from = '('.$from;
  1340. if ($from == ' AS ') return '';
  1341. return $from;
  1342. }
  1343. /**
  1344. * Parses all tables defined with CreateTable and creates the JOIN statements for the sql query.
  1345. * @see CreateTable, GetFromClause
  1346. */
  1347. public function parseSqlTableSetupChildren($parent,&$qryString) {
  1348. $paraCount = 0;
  1349. if (!is_array($parent)) return 0;
  1350. if (!array_key_exists('children',$parent)) return 0;
  1351. // $parent['children'] = array_reverse($parent['children']);
  1352. foreach ($parent['children'] as $child) {
  1353. $qryString.="\n {$child['joinType']} {$child['table']} AS {$child['alias']} ON ";
  1354. $joins = array();
  1355. foreach ($child['joins'] as $fromField => $toField) {
  1356. $ct = '=';
  1357. $fromFull = ($fromField[0] == "'" || $fromField[0] == '"' || stristr($fromField,'.') !== FALSE) ? $fromField : $parent['alias'].'.'.$fromField;//$child['alias'].'.'.$toField;
  1358. if (is_array($toField)) { // can process compare type also
  1359. $ct = $toField[0];
  1360. $toField = $toField[1];
  1361. $toFull = $toField;
  1362. } else
  1363. $toFull = ($toField[0] == "'" || $toField[0] == '"' || stristr($toField,'.') !== FALSE)? $toField : $child['alias'].'.'.$toField;
  1364. $joins[] = "$fromFull $ct $toFull";
  1365. }
  1366. $qryString.=join(' AND ',$joins);
  1367. $paraCount++;
  1368. $paraCount = $paraCount + $this->parseSqlTableSetupChildren($child,$qryString);
  1369. }
  1370. return $paraCount;
  1371. }
  1372. public function &recurseSqlSetupSearch(&$searchin,$searchfor) {
  1373. // is the current table?
  1374. if ($searchin['alias'] == $searchfor) { return $searchin; }
  1375. // if not, does it have children?
  1376. if (!empty($searchin['children'])) {
  1377. for ($i = 0, $maxCount = count($searchin['children']); $i < $maxCount; $i++) {
  1378. // check those children
  1379. if ($tbl =& $this->recurseSqlSetupSearch($searchin['children'][$i],$searchfor)) return $tbl;
  1380. }
  1381. }
  1382. $false = FALSE;
  1383. return $false;
  1384. }
  1385. public function GetSelectStatement() {
  1386. // init fields, get primary key, its required by all tables anyway so force it...
  1387. //grab the table alias and primary key from the alias's tabledef
  1388. $flds = array();
  1389. // $tblJoins = array();
  1390. // $tblInc = 1;
  1391. foreach ($this->fields as $alias => $fieldData) {
  1392. $str = $this->GetFieldLookupString($alias);
  1393. if (!empty($str)) $flds[] = $str." as `$alias`";
  1394. }
  1395. // table joins should be grouped by the link field.... eg:
  1396. // if table1.field1 is linked to table2.field2 in 2 lookups
  1397. // there is no need to create 2 table aliases, as they will return the same result set.
  1398. // $joins = '';
  1399. // foreach ($tblJoins as $tblJoin) {
  1400. // $joins .= " LEFT OUTER JOIN {$tblJoin['table']} {$tblJoin['ident']} ON {$tblJoin['ident']}.{$tblJoin['lookup']} = ".$this->GetPrimaryTable().".{$tblJoin['linkField']} ";
  1401. // }
  1402. // now create function to turn the sqlTableSetup into a FROM clause
  1403. $distinct = $this->flag_is_set(DISTINCT_ROWS) ? 'DISTINCT ' : '';
  1404. $qry = "$distinct".join(",\n",$flds);
  1405. return $qry;
  1406. }
  1407. public function &FindFilter($fieldName, $compareType=NULL, $inputType = NULL, $set = NULL) {
  1408. //$this->SetupParents();
  1409. // $this->_SetupFields();
  1410. // echo "FindFilter($fieldName, $compareType, $inputType): ";
  1411. //print_r($this->filters);
  1412. foreach ($this->filters as $ftypeID => $filterType) {
  1413. if ($set && $ftypeID !== $set) continue;
  1414. foreach ($filterType as $fsetID => $filterset) {
  1415. if (is_array($filterset)) foreach ($filterset as $arrID => $filterInfo) {
  1416. if ($filterInfo['uid'] === $fieldName) return $this->filters[$ftypeID][$fsetID][$arrID];
  1417. if ($filterInfo['fieldName'] != $fieldName) continue;
  1418. if ($compareType !== NULL && $filterInfo['ct'] !== $compareType) continue;
  1419. if ($inputType !== NULL && $filterInfo['it'] !== $inputType) continue;
  1420. return $this->filters[$ftypeID][$fsetID][$arrID];
  1421. }
  1422. }
  1423. }
  1424. // echo "not found<br/>";
  1425. $null = NULL;
  1426. return $null;
  1427. }
  1428. public function &GetFilterInfo($uid) {
  1429. // echo get_class($this).".GetFilterInfo($uid)<br/>";
  1430. foreach ($this->filters as $ftKey => $filterTypeArray) {
  1431. foreach ($filterTypeArray as $fsKey => $filterset) {
  1432. if (!is_array($filterset)) continue;
  1433. foreach ($filterset as $fk => $filterInfo) {
  1434. if ($filterInfo['uid'] == $uid) return $this->filters[$ftKey][$fsKey][$fk];
  1435. }
  1436. }
  1437. }
  1438. $null = NULL;
  1439. return $null;
  1440. }
  1441. public function RemoveFilter($uid) {
  1442. foreach ($this->filters as &$filterTypeArray) {
  1443. foreach ($filterTypeArray as &$filterset) {
  1444. if (!is_array($filterset)) continue;
  1445. foreach ($filterset as $k => &$filterInfo) {
  1446. if ($filterInfo['uid'] == $uid) {
  1447. unset($filterset[$k]);
  1448. return true;
  1449. }
  1450. }
  1451. }
  1452. }
  1453. }
  1454. public function GetFilterValue($uid, $refresh = FALSE) {
  1455. $filterData = $this->GetFilterInfo($uid);
  1456. if (!is_array($filterData)) return NULL;
  1457. if (isset($filterData['value'])) return $filterData['value'];
  1458. // for union modules, we cannot get a value from currentmodule because it is itself, part of the query
  1459. if ($filterData['it'] === itNONE && utopia::GetCurrentModule() !== get_class($this) && (!isset($this->UNION_MODULE) || $this->UNION_MODULE !== TRUE)) {
  1460. $parents = utopia::GetParents(get_class($this));
  1461. foreach ($parents as $parent => $childLinks) {
  1462. if ($parent == '/') $parent = utopia::GetCurrentModule();
  1463. if (!$parent || !class_exists($parent)) continue;
  1464. $parentObj = utopia::GetInstance($parent);
  1465. foreach ($childLinks as $info) {
  1466. if (isset($info['fieldLinks'])) {
  1467. foreach ($info['fieldLinks'] as $link) {
  1468. if ($link['toField'] == $filterData['fieldName']) {
  1469. $row = $parentObj->GetCurrentRecord($refresh);
  1470. if (!$row && !$refresh) $row = $parentObj->GetCurrentRecord(true);
  1471. if (isset($row[$link['fromField']])) return $row[$link['fromField']];
  1472. $fltrLookup =& $parentObj->FindFilter($link['fromField'],ctEQ);
  1473. return $parentObj->GetFilterValue($fltrLookup['uid']);
  1474. }
  1475. }
  1476. }
  1477. }
  1478. }
  1479. }
  1480. $filters = GetFilterArray();
  1481. if (isset($filters[$uid])) return $filters[$uid];
  1482. return $filterData['default'];
  1483. }
  1484. public function GetTableProperty($alias,$property) {
  1485. if (!isset($this->fields[$alias])) return NULL;
  1486. if (!isset($this->fields[$alias]['vtable'])) return NULL;
  1487. $tabledef = $this->fields[$alias]['vtable']['tModule'];
  1488. $fieldName = $this->fields[$alias]['field'];
  1489. $obj = utopia::GetInstance($tabledef);
  1490. return $obj->GetFieldProperty($fieldName,$property);
  1491. }
  1492. // filterSection = [where|having]
  1493. public function FormatFieldName($fieldName, $fieldType = NULL) {
  1494. if ($fieldType === NULL)
  1495. $fieldType = $this->GetFieldType($fieldName);
  1496. // do field
  1497. switch ($fieldType) {
  1498. case ('date'): $fieldName = "(STR_TO_DATE($fieldName,'".FORMAT_DATE."'))"; break;
  1499. case ('time'): $fieldName = "(STR_TO_DATE($fieldName,'".FORMAT_TIME."'))"; break;
  1500. case ('datetime'):
  1501. case ('timestamp'): $fieldName = "(STR_TO_DATE($fieldName,'".FORMAT_DATETIME."'))"; break;
  1502. default: break;
  1503. }
  1504. return $fieldName;
  1505. }
  1506. public function ReplaceFields($fieldToCompare) {
  1507. if (isset($this->fields[$fieldToCompare])) $fieldToCompare = '{'.$fieldToCompare.'}';
  1508. if (preg_match_all('/{([^}]+)}/',$fieldToCompare,$matches)) {
  1509. foreach ($matches[1] as $match) {
  1510. if (!isset($this->fields[$match])) continue;
  1511. $replace = null;
  1512. if (isset($this->fields[$match]['vtable']) && is_subclass_of($this->fields[$match]['vtable']['tModule'],'iLinkTable')) {
  1513. $replace = '`'.$this->fields[$match]['vtable']['alias'].'`.`'.$this->fields[$match]['field'].'`';
  1514. } else {
  1515. $replace = $this->GetFieldLookupString($match);//'`'.$this->fields[$match]['vtable']['alias'].'`.`'.$this->fields[$match]['field'].'`';
  1516. }
  1517. if ($replace !== null) $fieldToCompare = str_replace('{'.$match.'}',$replace,$fieldToCompare);
  1518. }
  1519. }
  1520. return $fieldToCompare;
  1521. }
  1522. public function GetFilterString($uid,&$args,$fieldNameOverride=NULL,$fieldTypeOverride=NULL){//,$filterSection) {
  1523. $filterData = $this->GetFilterInfo($uid);
  1524. if (!is_array($filterData)) return '';
  1525. $fieldName = $fieldNameOverride ? $fieldNameOverride : $filterData['fieldName'];
  1526. $compareType=$filterData['ct'];
  1527. $inputType=$filterData['it'];
  1528. if ($compareType === ctIGNORE) return '';
  1529. $value = $this->GetFilterValue($uid);//$filterData['value'];
  1530. $fieldToCompare = $fieldName;
  1531. if (is_array($fieldToCompare) && is_callable($fieldToCompare)) return $this->ReplaceFields(call_user_func_array($fieldToCompare,array($value,&$args)));
  1532. if ($filterData['type'] == FILTER_WHERE) {
  1533. $fieldToCompare = $this->ReplaceFields($fieldToCompare);
  1534. }
  1535. //echo "$uid::$value<br/>";
  1536. if (($value === NULL && $compareType !== ctCUSTOM) && (!isset($filterData['linkFrom']) || !$filterData['linkFrom'] || (!isset($_REQUEST['_f_'.$uid])))) return '';
  1537. // set filter VALUE
  1538. if ($compareType === ctLIKE && strpos($value,'%') === FALSE ) $value = "%$value%";
  1539. // find field type from tabledef
  1540. // set filter NAME
  1541. // if where, ignore type
  1542. $fieldName = $this->FormatFieldName($fieldName, $fieldTypeOverride);
  1543. // do value
  1544. switch (true) {
  1545. case $compareType == ctMATCH:
  1546. // any fields
  1547. $args = array_merge($args, (array)$value);
  1548. return 'MATCH ('.$fieldToCompare.') AGAINST (?)';
  1549. break;
  1550. case $compareType == ctMATCHBOOLEAN:
  1551. // any fields
  1552. $args = array_merge($args, (array)$value);
  1553. return 'MATCH ('.$fieldToCompare.') AGAINST (? IN BOOLEAN MODE)';
  1554. break;
  1555. case $compareType == ctCUSTOM:
  1556. if (($count = preg_match_all('/(?<!\\\)\?/',$fieldToCompare,$_))) { // has unescaped slashes, add values to args
  1557. $value = (array)$value;
  1558. $vcount = count($value);
  1559. if ($count === 1 && $vcount > 1) { // if count ==1 and count($value) > 1, repeat fTC and values for count(value)
  1560. $f = array();
  1561. for ($i = 0; $i < $vcount; $i++) $f[] = $fieldToCompare;
  1562. $fieldToCompare = '('.implode(' OR ',$f).')';
  1563. } else if ($count > 1 && $vcount === 1) { // if count >1 and count(value) 1, repeat value in args
  1564. $v = array();
  1565. for ($i = 0; $i < $count; $i++) $v[] = $value[0];
  1566. $value = $v;
  1567. }
  1568. $args = array_merge($args, $value);
  1569. }
  1570. return $fieldToCompare;
  1571. break;
  1572. case $compareType == ctANY:
  1573. $constants = get_defined_constants(true);
  1574. foreach ($constants['user'] as $cName => $cVal) {
  1575. if (strtolower(substr($cName,0,2))=='ct' && stripos($value,$cVal) !== FALSE) {
  1576. $val = $value;
  1577. break;
  1578. }
  1579. }
  1580. if (!$val) { $val = "= ?"; $args[] = $value; }
  1581. break;
  1582. case $compareType == ctIS:
  1583. case $compareType == ctISNOT:
  1584. $val = $value; break;
  1585. case $compareType == ctIN:
  1586. if (IsSelectStatement($fieldName)) return trim("$val $compareType $fieldName");
  1587. $vals = $value;
  1588. if (!is_array($vals)) $vals = explode(',',$vals);
  1589. $args = array_merge($args,$vals);
  1590. foreach ($vals as $k=>$v) $vals[$k] = '?';
  1591. $val = '('.join(',',$vals).')';
  1592. break;
  1593. // convert dates to mysql version for filter
  1594. case ($inputType==itDATE): $value = strftime(FORMAT_DATE,utopia::strtotime($value)); $val = "(STR_TO_DATE(?, '".FORMAT_DATE."'))"; $args[] = $value; break;
  1595. case ($inputType==itTIME): $value = strftime(FORMAT_TIME,utopia::strtotime($value)); $val = "(STR_TO_DATE(?, '".FORMAT_TIME."'))"; $args[] = $value; break;
  1596. case ($inputType==itDATETIME): $value = strftime(FORMAT_DATETIME,utopia::strtotime($value)); $val = "(STR_TO_DATE(?, '".FORMAT_DATETIME."'))"; $args[] = $value; break;
  1597. default:
  1598. $val = '?'; $args[] = $value;
  1599. break;
  1600. }
  1601. // $fieldToCompare = $fieldToCompare ? $fieldToCompare : $fieldName;
  1602. return trim("$fieldToCompare $compareType $val");
  1603. }
  1604. public $extraWhere = NULL;
  1605. public function GetWhereStatement(&$args) {
  1606. $filters = $this->filters;
  1607. $where = array();
  1608. if (isset($filters[FILTER_WHERE]))
  1609. foreach ($filters[FILTER_WHERE] as $filterset) {
  1610. $setParts = array();
  1611. if (!is_array($filterset)) continue;
  1612. foreach ($filterset as $fData) { // loop each field in set
  1613. if ($fData['type'] !== FILTER_WHERE) continue;
  1614. $fieldName = $fData['fieldName'];
  1615. // if the field doesnt exist in the primary table. -- should be ANY table used. and if more than one, should be specific.
  1616. if (($filterString = $this->GetFilterString($fData['uid'],$args)) !== '')
  1617. $setParts[] = "($filterString)";
  1618. }
  1619. if (count($setParts) >0) $where[] = '('.join(' AND ',$setParts).')';
  1620. }
  1621. $ret = join(' AND ',$where);
  1622. if (empty($this->extraWhere)) return $ret;
  1623. if (is_array($this->extraWhere)) {
  1624. $extraWhere = array();
  1625. foreach ($this->extraWhere as $field => $value) {
  1626. $args[] = $value;
  1627. $extraWhere[] = "($field = ?)";
  1628. }
  1629. return "($ret) AND (".implode(' AND ',$extraWhere).")";
  1630. } elseif (is_string($this->extraWhere)) {
  1631. return "($ret) AND (".$this->extraWhere.")";
  1632. }
  1633. return $ret;
  1634. }
  1635. public $extraHaving = NULL;
  1636. public function GetHavingStatement(&$args) {
  1637. $filters = $this->filters;
  1638. $having = array();
  1639. if (isset($filters[FILTER_HAVING]))
  1640. foreach ($filters[FILTER_HAVING] as $filterset) {
  1641. $setParts = array();
  1642. if (!is_array($filterset)) continue;
  1643. foreach ($filterset as $fData) { // loop each field in set
  1644. if ($fData['type'] !== FILTER_HAVING) continue;
  1645. $fieldName = $fData['fieldName'];
  1646. if (($filterString = $this->GetFilterString($fData['uid'],$args)) !== '')
  1647. $setParts[] = "($filterString)";
  1648. }
  1649. if (count($setParts) >0) $having[] = '('.join(' AND ',$setParts).')';
  1650. }
  1651. $ret = join(' OR ',$having);
  1652. if (empty($this->extraHaving)) return $ret;
  1653. if ($ret) $ret = "($ret) AND ";
  1654. if (is_array($this->extraHaving)) {
  1655. $extraWhere = array();
  1656. foreach ($this->extraHaving as $field => $value) {
  1657. $args = $value;
  1658. $extraWhere[] = "($field = ?)";
  1659. }
  1660. if (count($extraWhere) > 0) return "$ret (".implode(' AND ',$extraWhere).")";
  1661. } elseif (is_string($this->extraHaving) && $this->extraHaving) {
  1662. return "$ret (".$this->extraHaving.")";
  1663. }
  1664. return $ret;
  1665. }
  1666. public function GetOrderBy($as_array=false) {
  1667. $sortKey = '_s_'.$this->GetModuleId();
  1668. if (isset($_GET[$sortKey])) {
  1669. $this->ordering = NULL;
  1670. $this->AddOrderBy($_GET[$sortKey]);
  1671. }
  1672. if (empty($this->ordering)) return 'NULL';
  1673. if (is_array($this->ordering) && !$as_array) return join(', ',$this->ordering);
  1674. return $this->ordering;
  1675. }
  1676. public function GetGrouping() {
  1677. if (empty($this->grouping)) return '';
  1678. if (is_array($this->grouping)) return join(', ',$this->grouping);
  1679. return $this->grouping;
  1680. }
  1681. public $limit = NULL;
  1682. protected $queryChecksum = NULL;
  1683. protected $explainQuery = false;
  1684. /**
  1685. * Get a dataset based on setup.
  1686. * @returns MySQL Dataset
  1687. */
  1688. public function &GetDataset($filter=null,$clearFilters=false) {
  1689. $this->_SetupParents();
  1690. $this->_SetupFields();
  1691. if (!$this->bypassSecurity && !$this->flag_is_set(PERSISTENT) && uEvents::TriggerEvent('CanAccessModule',$this) === FALSE) { throw new Exception('Access denied to module '.get_class($this)); }
  1692. $this->dataset = new uDataset($this,$filter,$clearFilters);
  1693. return $this->dataset;
  1694. }
  1695. public function GetLimit(&$limit=null,&$page=null) {
  1696. if ($limit === '') $limit = NULL;
  1697. if ($limit === null) {
  1698. $limitKey = '_l_'.$this->GetModuleId();
  1699. if (isset($_GET[$limitKey])) $limit = $_GET[$limitKey];
  1700. }
  1701. if ($limit === null) $limit = $this->limit;
  1702. if ($limit === null) $limit = 10;
  1703. $pageKey = '_p_'.$this->GetModuleId();
  1704. if ($page === NULL) $page = isset($_GET[$pageKey]) ? (int)$_GET[$pageKey] : 0;
  1705. if (!is_numeric($limit)) $limit = NULL;
  1706. }
  1707. public function ApplyLimit(&$rows,$limit=null) {
  1708. // deprecated
  1709. }
  1710. public function GetCurrentRecord($refresh = FALSE) {
  1711. if (!$refresh) return $this->currentRecord;
  1712. if ($this->currentRecord !== NULL)
  1713. return $this->LookupRecord($this->currentRecord[$this->GetPrimaryKey()]);
  1714. return $this->currentRecord;
  1715. }
  1716. public function LookupRecord($filter=NULL,$clearFilters=false) {
  1717. if ($filter===NULL && $clearFilters===false && isset($_GET['_n_'.$this->GetModuleId()])) return NULL;
  1718. $ds = $this->GetDataset($filter,$clearFilters);
  1719. $row = $ds->GetFirst();
  1720. if (!$row) return NULL;
  1721. if ($filter===NULL && $clearFilters === FALSE) $this->currentRecord = $row;
  1722. return $row;
  1723. }
  1724. public function GetRowWhere($pkValue = NULL) {
  1725. if (!empty($pkValue)) return '`'.$this->GetPrimaryKey()."` = '".$pkValue."'";
  1726. return '';
  1727. }
  1728. public function GetRawData($filters=null) {
  1729. $this->_SetupFields();
  1730. $title = $this->GetTitle();
  1731. //TODO:sections
  1732. $layoutSections = $this->layoutSections;
  1733. $pk = $this->GetPrimaryKey();
  1734. $fieldDefinitions = array();
  1735. foreach ($this->fields as $fieldAlias => $fieldData) {
  1736. $fieldDefinitions[$fieldAlias] = array('title'=>$fieldData['visiblename'],'type'=>$fieldData['inputtype']);
  1737. }
  1738. $data = $this->GetDataset($filters)->fetchAll();
  1739. return array(
  1740. 'definition' => array('title'=>$title,'fields'=>$fieldDefinitions,'pk'=>$pk),
  1741. 'data' => $data,
  1742. );
  1743. }
  1744. // private $navCreated = FALSE;
  1745. // sends ARGS,originalValue,pkVal,processedVal
  1746. public function PreProcess($fieldName,$value,$rec=NULL,$forceType = NULL) {
  1747. $realType = $this->GetFieldType($fieldName);
  1748. $pkVal = !is_null($rec) ? $rec[$this->GetPrimaryKey()] : NULL;
  1749. if (is_string($value) && $realType !== ftFILE) $value = mb_convert_encoding($value, 'HTML-ENTITIES', CHARSET_ENCODING);
  1750. $originalValue = $value;
  1751. $suf = ''; $pre = ''; $isNumeric=true;
  1752. if ($forceType === NULL) $forceType = $realType;
  1753. switch ($forceType) {
  1754. case ftFILE:
  1755. if ($value) {
  1756. $filename = ($rec && isset($rec[$fieldName.'_filename']) && $rec[$fieldName.'_filename']) ? $rec[$fieldName.'_filename'] : 'Download';
  1757. $link = uBlob::GetLink(get_class($this),$fieldName,$pkVal,$filename);
  1758. $link = '<b><a target="_blank" href="'.$link.'">'.$filename.'</a></b> - ';
  1759. $value = $link.round(strlen($value)/1024,2).'Kb<br/>';
  1760. }
  1761. break;
  1762. case ftIMAGE:
  1763. if (!$value) break;
  1764. $style = $this->FieldStyles_Get($fieldName,$rec);
  1765. $w = isset($style['width']) ? intval($style['width']) : null;
  1766. $h = isset($style['height']) ? intval($style['height']) : null;
  1767. $value = $this->DrawSqlImage($fieldName,$pkVal,$w,$h,array('style'=>$style));
  1768. break;
  1769. case ftPERCENT:
  1770. $dp = $this->GetFieldProperty($fieldName,'length');
  1771. if (strpos($dp,',') != FALSE) $dp = substr($dp,strpos($dp,',')+1);
  1772. else $dp = 2;
  1773. if (is_numeric($value))
  1774. $value = number_format($value,$dp).'%';
  1775. break;
  1776. case ftFLOAT:
  1777. $dp = $this->GetFieldProperty($fieldName,'length');
  1778. if (is_numeric($value) && strpos($dp,',') != FALSE)
  1779. $value = number_format($value,substr($dp,strpos($dp,',')+1));
  1780. break;
  1781. case ftUPLOAD:
  1782. if ($value) {
  1783. $url = uBlob::GetLink(get_class($this),$fieldName,$pkVal);
  1784. $value = "<a href=\"$url\">Download</a>";
  1785. }
  1786. break;
  1787. default: $isNumeric = false;
  1788. }
  1789. if (array_key_exists($fieldName,$this->fields) && array_key_exists('preprocess',$this->fields[$fieldName])) {
  1790. foreach ($this->fields[$fieldName]['preprocess'] as $callback) {
  1791. $args = $callback[1];
  1792. $callback = $callback[0];
  1793. if (is_string($callback)) {
  1794. $method = new ReflectionFunction($callback);
  1795. } elseif (is_array($callback)) {
  1796. $method = new ReflectionMethod($callback[0],$callback[1]);
  1797. }
  1798. $num = $method->getNumberOfParameters();
  1799. if ($args == NULL) $args = array();
  1800. array_unshift($args,$originalValue,$pkVal,$value,$rec,$fieldName);
  1801. array_splice($args,$num); // strip out any extra args than the function can take (stops 'wrong param count' error)
  1802. $nv = call_user_func_array($callback,$args);
  1803. if ($nv !== null) $value = $nv;
  1804. }
  1805. }
  1806. return $value;
  1807. }
  1808. // TODO: Requests for XML data (ajax)
  1809. // to be used later with full AJAX implimentation
  1810. public function CreateXML() {
  1811. // call parent createxml
  1812. // parse and inject child links into 'dataset' xml object
  1813. }
  1814. public function GetFilterBox($filterInfo,$attributes=NULL,$spanAttributes=NULL) {
  1815. if (is_string($filterInfo))
  1816. $filterInfo = $this->GetFilterInfo($filterInfo);
  1817. // already filtered?
  1818. if ($filterInfo['it'] === itNONE) return '';
  1819. $fieldName = $filterInfo['fieldName'];
  1820. $default = $this->GetFilterValue($filterInfo['uid']);
  1821. $pre = '';
  1822. $emptyVal = '';
  1823. if (!empty($filterInfo['title'])) {
  1824. $emptyVal = $filterInfo['title'];
  1825. } elseif (isset($this->fields[$fieldName])) {
  1826. switch ($filterInfo['ct']) {
  1827. case ctGT:
  1828. case ctGTEQ:
  1829. $emptyVal = $this->fields[$fieldName]['visiblename'].' After'; break;
  1830. case ctLT:
  1831. case ctLTEQ:
  1832. $emptyVal = $this->fields[$fieldName]['visiblename'].' Before'; break;
  1833. case ctEQ:
  1834. case ctLIKE:
  1835. $emptyVal = $this->fields[$fieldName]['visiblename']; break;
  1836. default:
  1837. $emptyVal = $this->fields[$fieldName]['visiblename'].' '.htmlentities($filterInfo['ct']); break;
  1838. }
  1839. }
  1840. //$vals = $filterInfo['values'];
  1841. //if (!is_array($vals))
  1842. $vals = $this->GetValues($filterInfo['uid']);
  1843. if (isset($vals['']) && $vals[''] === FALSE && isset($vals[$filterInfo['default']])) {
  1844. $vals[''] = $vals[$filterInfo['default']];
  1845. if ($default == $filterInfo['default']) $default = '';
  1846. unset($vals[$filterInfo['default']]);
  1847. }
  1848. if (!$attributes) $attributes = array();
  1849. if (isset($filterInfo['attributes'])) $attributes = array_merge((array)$filterInfo['attributes'],$attributes);
  1850. $attributes['placeholder'] = trim(strip_tags($emptyVal));
  1851. if (array_key_exists('class',$attributes)) $attributes['class'] .= ' uFilter';
  1852. else $attributes['class'] = 'uFilter';
  1853. $spanAttr = BuildAttrString($spanAttributes);
  1854. return '<span '.$spanAttr.'>'.$pre.utopia::DrawInput('_f_'.$filterInfo['uid'],$filterInfo['it'],$default,$vals,$attributes,false).'</span>';
  1855. }
  1856. public function ProcessUpdates($sendingField,$fieldAlias,$value,&$pkVal=NULL,$isFile=false) {
  1857. $this->_SetupFields();
  1858. $opk = NULL;
  1859. // can we access this field?
  1860. if ($pkVal !== NULL && !$this->LookupRecord($pkVal)) throw new Exception('Unable update a field that you cannot read');
  1861. if ($fieldAlias === '__u_delete_record__')
  1862. $this->DeleteRecord($pkVal);
  1863. elseif ($isFile)
  1864. $this->UploadFile($fieldAlias,$value,$pkVal);
  1865. else
  1866. $this->UpdateField($fieldAlias,$value,$pkVal);
  1867. foreach ($this->fields as $alias => $field) {
  1868. if ($alias == $fieldAlias) continue;
  1869. if (!isset($field['preprocess']) && !$field['values'] && (isset($this->fields[$fieldAlias]) && $this->fields[$fieldAlias]['field'] != $field['field'])) continue;
  1870. $this->ResetField($alias,$pkVal);
  1871. }
  1872. }
  1873. public function DeleteRecord($pkVal) {
  1874. if (!$this->flag_is_set(ALLOW_DELETE)) { throw new Exception('Module does not allow record deletion'); }
  1875. if (uEvents::TriggerEvent('BeforeDeleteRecord',$this,array($pkVal)) === FALSE) return FALSE;
  1876. $table = TABLE_PREFIX.$this->GetTabledef();
  1877. database::query("DELETE FROM $table WHERE `{$this->GetPrimaryKey()}` = ?",array($pkVal));
  1878. uEvents::TriggerEvent('AfterDeleteRecord',$this,array($pkVal));
  1879. return TRUE;
  1880. }
  1881. public function UploadFile($fieldAlias,$fileInfo,&$pkVal = NULL) {
  1882. //$allowedTypes = $this->GetFieldProperty($fieldAlias, 'allowed');
  1883. if (uEvents::TriggerEvent('BeforeUploadFile',$this,array($fieldAlias,$fileInfo,&$pkVal)) === FALSE) return FALSE;
  1884. if (!file_exists($fileInfo['tmp_name'])) { AjaxEcho('alert("File too large. Maximum File Size: '.utopia::ReadableBytes(utopia::GetMaxUpload()).'");'); $this->ResetField($fieldAlias,$pkVal); return; }
  1885. $this->UpdateField($fieldAlias.'_filename',$fileInfo['name'],$pkVal);
  1886. $this->UpdateField($fieldAlias.'_filetype',$fileInfo['type'],$pkVal);
  1887. $type = $this->GetFieldType($fieldAlias);
  1888. if ($type === ftFILE || $type === ftIMAGE) {
  1889. $value = file_get_contents($fileInfo['tmp_name']);
  1890. $this->UpdateField($fieldAlias,$value,$pkVal);
  1891. } else {
  1892. $targetFile = md5_file($fileInfo['tmp_name']).sha1_file($fileInfo['tmp_name']);
  1893. $targetPath = 'uFiles/'.substr($targetFile,0,3).'/'.substr($targetFile,3,3).'/';//.date('Y').'/'.date('m-d').'/';
  1894. if (!file_exists(PATH_ABS_ROOT.$targetPath)) mkdir(PATH_ABS_ROOT.$targetPath,0777,true);
  1895. if (!file_exists(PATH_ABS_ROOT.$targetPath.$targetFile)) copy($fileInfo['tmp_name'],PATH_ABS_ROOT.$targetPath.$targetFile);
  1896. $this->UpdateField($fieldAlias,$targetPath.$targetFile,$pkVal);
  1897. }
  1898. if (uEvents::TriggerEvent('AfterUploadFile',$this,array($fieldAlias,$fileInfo,&$pkVal)) === FALSE) return FALSE;
  1899. }
  1900. public function OnNewRecord($pkValue) {}
  1901. public function OnParentNewRecord($pkValue) {}
  1902. public function UpdateFields($fieldsVals,&$pkVal=NULL) {
  1903. foreach ($fieldsVals as $field => $val) {
  1904. $this->UpdateField($field,$val,$pkVal);
  1905. }
  1906. }
  1907. public function UpdateFieldRaw($fieldAlias,$newValue,&$pkVal=NULL) {
  1908. $fieldType = $this->SetFieldType($fieldAlias,ftRAW);
  1909. $ret = $this->UpdateField($fieldAlias,$newValue,$pkVal);
  1910. $this->SetFieldType($fieldAlias,$fieldType);
  1911. return $ret;
  1912. }
  1913. // returns a string pointing to a new url, TRUE if the update succeeds, false if it fails, and null to refresh the page
  1914. protected $noDefaults = FALSE;
  1915. public function UpdateField($fieldAlias,$newValue,&$pkVal=NULL) {
  1916. $this->_SetupFields();
  1917. if (!array_key_exists($fieldAlias,$this->fields)) { return; }
  1918. if ($pkVal === NULL && !$this->flag_is_set(ALLOW_ADD,$fieldAlias)) { throw new Exception('Module does not allow adding records'); }
  1919. if ($pkVal !== NULL && !$this->flag_is_set(ALLOW_EDIT,$fieldAlias)) { throw new Exception('Module does not allow editing records'); }
  1920. if (!$this->bypassSecurity && !$this->flag_is_set(PERSISTENT,$fieldAlias) && uEvents::TriggerEvent('CanAccessModule',$this) === FALSE) { throw new Exception('Access Denied when attempting to update field'); }
  1921. $tableAlias = $this->fields[$fieldAlias]['tablename'];
  1922. if (!$tableAlias) return FALSE; // cannot update a field that has no table
  1923. if (uEvents::TriggerEvent('BeforeUpdateField',$this,array($fieldAlias,$newValue,&$pkVal)) === FALSE) {
  1924. $this->ResetField($fieldAlias,$pkVal);
  1925. return FALSE;
  1926. }
  1927. $oldPkVal = $pkVal;
  1928. $fieldPK = $this->GetPrimaryKey($fieldAlias);
  1929. $tbl = $this->fields[$fieldAlias]['vtable'];
  1930. $values = $this->GetValues($fieldAlias,$pkVal);
  1931. $fieldType = $this->GetFieldType($fieldAlias);
  1932. if ($this->fields[$fieldAlias]['inputtype'] == itPASSWORD && $fieldType !== ftRAW) {
  1933. if (empty($newValue)) return FALSE;
  1934. $newValue = uCrypt::Encrypt($newValue);
  1935. }
  1936. $originalValue = $newValue;
  1937. $field = $this->fields[$fieldAlias]['field'];
  1938. $table = $tbl['tModule'];
  1939. $tablePk = $tbl['pk'];
  1940. if ((preg_match('/{[^}]+}/',$field) > 0) || IsSelectStatement($field) || is_array($field)) {
  1941. $this->ResetField($fieldAlias,$pkVal);
  1942. return FALSE; // this field is a pragma, select statement or callback
  1943. }
  1944. $preModPk = NULL;
  1945. if ($table !== $this->GetTabledef()) {
  1946. if ($pkVal === NULL) { // current module PK if not row exists, create it
  1947. $this->UpdateField($this->GetPrimaryKey(),null,$pkVal);
  1948. }
  1949. $row = $this->LookupRecord($pkVal,true);
  1950. $pkLinkTo = null; $pkLinkFrom = null;
  1951. $pkValTo = null; $pkValFrom = null;
  1952. foreach ($tbl['joins'] as $fromField=>$toField) {
  1953. if ($toField == $this->sqlTableSetupFlat[$tbl['parent']]['pk']) {
  1954. $pkLinkFrom = $fromField; $pkLinkTo = $toField;
  1955. // from (parent) pk / to (child) pk
  1956. foreach ($this->fields as $_f => $_finfo) {
  1957. if ($_finfo['tablename'] == $this->sqlTableSetupFlat[$tbl['parent']]['alias'] && $_finfo['field'] == $fromField)
  1958. $pkValFrom = $row[$_f];
  1959. elseif ($_finfo['tablename'] == $this->sqlTableSetupFlat[$tbl['alias']]['alias'] && $_finfo['field'] == $toField)
  1960. $pkValTo = $row[$_f];
  1961. }
  1962. }
  1963. }
  1964. $tableObj = utopia::GetInstance($table);
  1965. if ($pkValTo === NULL && $pkValFrom) {
  1966. $tableObj->UpdateField($pkLinkTo,$pkValFrom);
  1967. $row = $this->LookupRecord($pkVal,true);
  1968. }
  1969. $tableObj = utopia::GetInstance($table);
  1970. if ($tableObj instanceof iLinkTable) {
  1971. // delete all where tofield is oldpk
  1972. database::query('DELETE FROM `'.$tableObj->tablename.'` WHERE `'.$pkLinkTo.'` = ?',array($pkVal));
  1973. // loop through new values (unless empty) and add them to the link table
  1974. if ($newValue !== NULL && $newValue !== '') {
  1975. if (!is_array($newValue)) $newValue = array($newValue);
  1976. foreach ($newValue as $v) {
  1977. $n = null;
  1978. $tableObj->UpdateField($pkLinkTo,$pkVal,$n,$fieldType); //set left
  1979. $tableObj->UpdateField($field,$v,$n,$fieldType); //set right
  1980. }
  1981. }
  1982. return true;
  1983. }
  1984. // pk of table
  1985. $preModPk = $pkVal;
  1986. $pkVal = $row['_'.$tableAlias.'_pk'];
  1987. if ($pkVal === NULL) {
  1988. // linked target does not exist, create it
  1989. if ($pkLinkTo == $field) {
  1990. $tableObj->UpdateField($pkLinkTo,$newValue,$pkVal,$fieldType);
  1991. } else {
  1992. $tableObj->UpdateField($field,$newValue,$pkVal,$fieldType);
  1993. }
  1994. foreach ($this->fields as $_f => $_finfo) { // set pkLinkFrom to newly created record in linked table
  1995. if (isset($_finfo['vtable']) && $_finfo['vtable']['tModule'] == $this->GetTabledef() && $_finfo['field'] == $pkLinkFrom) {
  1996. $this->UpdateField($_f,$pkVal,$preModPk);
  1997. break;
  1998. }
  1999. }
  2000. }
  2001. }
  2002. // lets update the field
  2003. $tableObj = utopia::GetInstance($table);
  2004. try {
  2005. $ret = $tableObj->UpdateField($field,$newValue,$pkVal,$fieldType) === FALSE ? FALSE : TRUE;
  2006. } catch (Exception $e) {
  2007. $ret = false;
  2008. switch ($e->getCode()) {
  2009. case 1062: // duplicate key
  2010. uNotices::AddNotice('An entry already exists with this value.',NOTICE_TYPE_ERROR);
  2011. break;
  2012. default: throw $e;
  2013. }
  2014. }
  2015. if ($preModPk !== NULL) $pkVal = $preModPk;
  2016. if ($oldPkVal === NULL) {
  2017. // new record added
  2018. // update default values
  2019. if (!$this->noDefaults) {
  2020. $this->noDefaults = true;
  2021. foreach ($this->fields as $dalias => $fieldData) {
  2022. if ($fieldAlias == $dalias) continue; // dont update the default for the field which is being set.
  2023. if ($dalias == $this->GetPrimaryKey()) continue;
  2024. $default = $this->GetDefaultValue($dalias);
  2025. if (!empty($default)) {
  2026. //echo "//setting default for $dalias to $default PK $pkVal\n";
  2027. $this->UpdateField($dalias,$default,$pkVal);
  2028. }
  2029. }
  2030. $this->noDefaults = false;
  2031. }
  2032. // new record has been created. pass the info on to child modules, incase they need to act on it.
  2033. uEvents::TriggerEvent('OnNewRecord',$this,$pkVal);
  2034. }
  2035. if (array_key_exists('onupdate',$this->fields[$fieldAlias])) {
  2036. foreach ($this->fields[$fieldAlias]['onupdate'] as $callback) {
  2037. list($callback,$arr) = $callback;
  2038. //echo "$callback,".print_r($arr,true);
  2039. if (is_string($callback))// $callback = array($this,$callback);
  2040. $callback = array($this,$callback);
  2041. array_unshift($arr,$pkVal);
  2042. $newRet = call_user_func_array($callback,$arr);
  2043. if ($ret === TRUE) $ret = $newRet;
  2044. }
  2045. }
  2046. $this->ResetField($fieldAlias,$pkVal);
  2047. if ($oldPkVal !== $pkVal) $this->ResetField($fieldAlias,$oldPkVal);
  2048. if (uEvents::TriggerEvent('AfterUpdateField',$this,array($fieldAlias,$newValue,&$pkVal)) === FALSE) return FALSE;
  2049. return $ret;
  2050. }
  2051. public function MergeFields(&$string,$row) {
  2052. $fields = $this->fields;
  2053. if (preg_match_all('/{([a-z]+)\.([^{]+)}/Ui',$string,$matches,PREG_PATTERN_ORDER)) {
  2054. $searchArr = $matches[0];
  2055. $typeArr = isset($matches[1]) ? $matches[1] : false;
  2056. $varsArr = isset($matches[2]) ? $matches[2] : false;
  2057. $row['_module_url'] = $this->GetURL($row[$this->GetPrimaryKey()]);
  2058. foreach ($searchArr as $k => $search) {
  2059. $field = $varsArr[$k];
  2060. $qs = null;
  2061. if (strpos($field,'?') !== FALSE) list($field,$qs) = explode('?',$field,2);
  2062. if (!array_key_exists($field,$row)) continue;
  2063. if ($qs) {
  2064. parse_str(html_entity_decode($qs),$qs);
  2065. $this->FieldStyles_Add($field,$qs);
  2066. }
  2067. switch ($typeArr[$k]) {
  2068. case 'urlencode':
  2069. $replace = $this->PreProcess($field,$row[$field],$row);
  2070. $replace = rawurlencode($replace);
  2071. $string = str_replace($search,$replace,$string);
  2072. break;
  2073. case 'd':
  2074. $replace = $this->GetCell($field,$row);
  2075. $string = str_replace($search,$replace,$string);
  2076. break;
  2077. case 'field':
  2078. $replace = $this->PreProcess($field,$row[$field],$row);
  2079. $string = str_replace($search,$replace,$string);
  2080. break;
  2081. default:
  2082. }
  2083. }
  2084. }
  2085. $this->fields = $fields;
  2086. while (utopia::MergeVars($string));
  2087. }
  2088. public function DrawSqlImage($fieldAlias,$pkVal,$width=NULL,$height=NULL,$attr=NULL,$link=false,$linkW=NULL,$linkH=NULL,$linkAttr=NULL) {
  2089. if (!is_array($attr)) $attr = array();
  2090. if (!array_key_exists('alt',$attr)) $attr['alt'] = '';
  2091. if ($width) $attr['width'] = intval($width); if ($height) $attr['height'] = intval($height);
  2092. if (isset($attr['class'])) $attr['class'] .= ' field-'.$fieldAlias;
  2093. else $attr['class'] = 'field-'.$fieldAlias;
  2094. $attr = BuildAttrString($attr);
  2095. $url = uBlob::GetLink(get_class($this),$fieldAlias,$pkVal);
  2096. $imgQ = http_build_query(array('w'=>$width,'h'=>$height)); if ($imgQ) $imgQ = '?'.$imgQ;
  2097. if (!$link) return "<img$attr src=\"$url$imgQ\">";
  2098. $linkQ = http_build_query(array('w'=>$linkW,'h'=>$linkH)); if ($linkQ) $linkQ = '?'.$linkQ;
  2099. if ($link === TRUE) $linkUrl = $url.$linkQ;
  2100. else $linkUrl = $link;
  2101. if (isset($linkAttr['class'])) $linkAttr['class'] .= ' field-'.$fieldAlias;
  2102. else $linkAttr['class'] = 'field-'.$fieldAlias;
  2103. $linkAttr = BuildAttrString($linkAttr);
  2104. return "<a$linkAttr href=\"$linkUrl\" target=\"_blank\"><img$attr src=\"$url\"></a>";
  2105. }
  2106. public function GetCell($fieldName, $row, $url = '', $inputTypeOverride=NULL, $valuesOverride=NULL) {
  2107. if (is_array($row) && array_key_exists('__module__',$row) && $row['__module__'] != get_class($this)) {
  2108. $obj = utopia::GetInstance($row['__module__']);
  2109. return $obj->GetCell($fieldName,$row,$url,$inputTypeOverride,$valuesOverride);
  2110. }
  2111. if ($this->UNION_MODULE)
  2112. $pkField = '__module_pk__';
  2113. else
  2114. $pkField = $this->GetPrimaryKey();
  2115. $fldId = $this->GetEncodedFieldName($fieldName,$row[$pkField]);
  2116. $celldata = $this->GetCellData($fieldName,$row,$url,$inputTypeOverride,$valuesOverride);
  2117. return "<!-- NoProcess --><span class=\"$fldId\">$celldata</span><!-- /NoProcess -->";
  2118. }
  2119. public function GetCellData($fieldName, $row, $url = '', $inputTypeOverride=NULL, $valuesOverride=NULL) {
  2120. if (is_array($row) && array_key_exists('__module__',$row) && $row['__module__'] != get_class($this)) {
  2121. $obj = utopia::GetInstance($row['__module__']);
  2122. return $obj->GetCellData($fieldName,$row,$url,$inputTypeOverride,$valuesOverride);
  2123. }
  2124. $pkVal = NULL;
  2125. if (is_array($row)) {
  2126. if ($this->UNION_MODULE)
  2127. $pkField = '__module_pk__';
  2128. else
  2129. $pkField = $this->GetPrimaryKey();
  2130. $pkVal = $row[$pkField];
  2131. }
  2132. // echo "// start PP for $fieldName ".(is_array($row) && array_key_exists($fieldName,$row) ? $row[$fieldName] : '')."\n";
  2133. $value = '';
  2134. if (isset($row[$fieldName])) $value = $row[$fieldName];
  2135. if ($value === '' && isset($this->fields[$fieldName]) && preg_match('/^\'(.+?)\'/', $this->fields[$fieldName]['field'],$match)) $value = $match[1];
  2136. $value = $this->PreProcess($fieldName,$value,$row);
  2137. $fieldData = array();
  2138. if (isset($this->fields[$fieldName])) $fieldData = $this->fields[$fieldName];
  2139. if (!$fieldData && strpos($fieldName,':') !== FALSE) {
  2140. list($vname) = explode(':',$fieldName);
  2141. if (isset($this->fields[$vname])) $fieldData = $this->fields[$vname];
  2142. }
  2143. $attr = array();
  2144. $styles = $this->FieldStyles_Get($fieldName,$row);
  2145. if ($styles) $attr['style'] = $styles;
  2146. $inputType = !is_null($inputTypeOverride) ? $inputTypeOverride : (isset($fieldData['inputtype']) ? $fieldData['inputtype'] : itNONE);
  2147. if ($inputType !== itNONE && ($inputTypeOverride || ($row !== NULL && $this->flag_is_set(ALLOW_EDIT,$fieldName)) || ($row === NULL && $this->flag_is_set(ALLOW_ADD,$fieldName)))) {
  2148. if ($inputType === itFILE) {
  2149. $ret = '';
  2150. if (!$value) {
  2151. $ret .= '<span class="icon-document-delete uDesaturate"></span>';
  2152. $ret .= '<span class="icon-document-view uDesaturate"></span>';
  2153. $ret .= '<span class="icon-document-download uDesaturate"></span>';
  2154. } else {
  2155. $id = $this->CreateSqlField($fieldName,$pkVal);
  2156. $ret .= '<a title="Delete File" href="#" name="'.$id.'" class="uf icon-document-delete"></a>';
  2157. $link = uBlob::GetLink(get_class($this),$fieldName,$pkVal,$row[$fieldName.'_filename']);
  2158. $ret .= '<a title="View File" target="_blank" href="'.$link.'" class="icon-document-view"></a>';
  2159. $ret .= '<a title="Download File" href="'.$link.'?attach=attachment" class="icon-document-download"></a>';
  2160. }
  2161. $ret .= '<span title="Upload File" class="icon-document-upload">'.$this->DrawSqlInput($fieldName,$value,$pkVal,$attr,$inputType,$valuesOverride).'</span>';
  2162. } else {
  2163. if ($pkVal === NULL) { // set a placeholder based on the default value for new records
  2164. $dv = $this->GetDefaultValue($fieldName);
  2165. $vals = $valuesOverride;
  2166. if (!$vals) $vals = $this->GetValues($fieldName,$pkVal);
  2167. if ($dv && $vals && isset($vals[$dv])) $dv = $vals[$dv];
  2168. if (!$value) $value = $dv;
  2169. $attr['placeholder'] = $value;
  2170. }
  2171. $ret = $this->DrawSqlInput($fieldName,$value,$pkVal,$attr,$inputType,$valuesOverride);
  2172. }
  2173. } else {
  2174. $vals = $valuesOverride;
  2175. if (!$vals) $vals = $this->GetValues($fieldName);
  2176. if (isset($vals[$value])) $value = $vals[$value];
  2177. $ret = '';
  2178. if ($url && !$this->GetFieldProperty($fieldName,'nolink')) {
  2179. if ($this->GetFieldProperty($fieldName,'button')) $attr['class'] = isset($attr['class'])? $attr['class'].' btn' : 'btn';
  2180. $attrStr = BuildAttrString($attr);
  2181. $ret = "<a$attrStr href=\"$url\">$value</a>";
  2182. } else {
  2183. $attrStr = BuildAttrString($attr);
  2184. $ret = $value;
  2185. if ($attrStr) $ret = "<span$attrStr>$value</span>";
  2186. }
  2187. }
  2188. return $ret;
  2189. }
  2190. static $targetChildren = array();
  2191. public function GetTargetURL($field,$row,$includeFilter = true) {
  2192. $fURL = $this->GetFieldProperty($field,'url');
  2193. if ($fURL) return $fURL;
  2194. // check union module
  2195. $searchModule = is_array($row) && array_key_exists('__module__',$row) ? $row['__module__'] : get_class($this);
  2196. // print_r($GLOBALS['children']);
  2197. //echo "$searchModule<br/>";
  2198. $children = isset(self::$targetChildren[$searchModule]) ? self::$targetChildren[$searchModule] : utopia::GetChildren($searchModule);
  2199. $info = NULL;
  2200. // get specific field
  2201. foreach ($children as $links) {
  2202. foreach ($links as $link) {
  2203. if (!isset($link['parentField'])) continue;
  2204. if ($link['parentField'] == $field) { $info = $link; break; }
  2205. }
  2206. }
  2207. // if not found, check for fallback
  2208. if (!$info) {
  2209. if ($field !== '*') return $this->GetTargetURL('*',$row,$includeFilter);
  2210. return NULL;
  2211. }
  2212. if ($includeFilter) {
  2213. $targetFilter = $this->GetTargetFilters($field,$row);
  2214. } else {
  2215. $targetFilter = NULL;
  2216. }
  2217. $obj = utopia::GetInstance($info['child']);
  2218. return $obj->GetURL($targetFilter);
  2219. }
  2220. public function GetTargetFilters($field,$row) {
  2221. // ErrorLog("GTF($field,$row)");
  2222. if ($row == NULL) return NULL;
  2223. $searchModule = is_array($row) && array_key_exists('__module__',$row) ? $row['__module__'] : get_class($this);
  2224. $children = isset(self::$targetChildren[$searchModule]) ? self::$targetChildren[$searchModule] : utopia::GetChildren($searchModule);
  2225. $info = NULL;
  2226. // get specific field
  2227. foreach ($children as $links) {
  2228. foreach ($links as $link) {
  2229. if (!isset($link['parentField'])) continue;
  2230. if ($link['parentField'] == $field) { $info = $link; break; }
  2231. }
  2232. }
  2233. // if not found, check for fallback
  2234. if (!$info) {
  2235. if ($field !== '*') return $this->GetTargetFilters('*',$row);
  2236. return NULL;
  2237. }
  2238. //echo "<br/>$field:";
  2239. // fieldLinks: array: parentField => childField
  2240. // need to replace the values
  2241. $targetModule = $info['child'];
  2242. $newFilter = array();
  2243. // $additional = array();
  2244. //print_r($info['fieldLinks']);
  2245. foreach ($info['fieldLinks'] as $linkInfo) {
  2246. $value = NULL;
  2247. // fromfield == mortgage_id
  2248. // module_pk == VAL:note_id
  2249. if (!$this->FieldExists($linkInfo['fromField']) and ($fltr =& $this->FindFilter($linkInfo['fromField']))) {
  2250. // no field exists, but we do have a filter, we should move that over
  2251. $value = $this->GetFilterValue($fltr['uid']);
  2252. // if its a union AND the fromfield equals the modules primarykey then show the module_pk
  2253. // NO, if its a union, loop thru that modules fields to find the number of the fromField. then get the value of the corresponding number in this union parent module
  2254. } elseif (array_key_exists('__module__',$row)) {
  2255. $obj = utopia::GetInstance($row['__module__']);
  2256. $unionFields = $obj->fields;
  2257. $uFieldCount = 0;
  2258. $keys = array_keys($unionFields);
  2259. foreach ($keys as $uFieldAlias) {
  2260. if ($uFieldAlias == $linkInfo['fromField']) break;
  2261. $uFieldCount++;
  2262. }
  2263. $ourKeys = array_keys($this->fields);
  2264. $correspondingKey = $ourKeys[$uFieldCount];
  2265. $value = $row[$correspondingKey];
  2266. } else {
  2267. $value = $row[$linkInfo['fromField']]; // use actual value, getting the real value on every field causes a lot of lookups, the requested field must be the field that stores the actual value
  2268. }
  2269. //echo $value."<br/>";
  2270. if ($value !== NULL) {
  2271. $obj = utopia::GetInstance($targetModule);
  2272. $fltr = $obj->FindFilter($linkInfo['toField']);
  2273. if ($fltr) $newFilter['_f_'.$fltr['uid']] = $value;
  2274. }
  2275. }
  2276. return $newFilter;
  2277. //print_r(array_merge($newFilter,$additional));
  2278. return array_merge($newFilter,$additional);
  2279. // now returns an array
  2280. $extra = array();
  2281. if (is_array($additional)) foreach ($additional as $key => $val)
  2282. $extra[] = "$key=$val";
  2283. array_unshift($extra,FilterArrayToString($newFilter));
  2284. return join('&amp;',$extra);
  2285. }
  2286. public function ResetField($fieldAlias,$pkVal = NULL) {
  2287. if (!$this->FieldExists($fieldAlias)) return;
  2288. if (uEvents::TriggerEvent('BeforeResetField',$this,$fieldAlias) === FALSE) return FALSE;
  2289. // reset the field.
  2290. $enc_name = $this->GetEncodedFieldName($fieldAlias,$pkVal);
  2291. $newRec = is_null($pkVal) ? NULL : $this->LookupRecord($pkVal,true);
  2292. $data = $this->GetCellData($fieldAlias,$newRec,$this->GetTargetURL($fieldAlias,$newRec));
  2293. utopia::AjaxUpdateElement('.'.$enc_name,$data);
  2294. // if this field is the PK of a linked table also update all fields associated with that table
  2295. $info = $this->fields[$fieldAlias];
  2296. $tbl = array();
  2297. foreach ($this->sqlTableSetupFlat as $t) {
  2298. if (isset($t['joins'])) foreach ($t['joins'] as $from => $to) {
  2299. if ($from == $info['field'] || $to == $info['field']) $tbl[] = $t['alias'];
  2300. }
  2301. }
  2302. foreach ($this->fields as $alias => $info) {
  2303. if (array_search($info['tablename'],$tbl) === false) continue;
  2304. $enc_name = $this->GetEncodedFieldName($alias,$pkVal);
  2305. $data = $this->GetCellData($alias,$newRec,$this->GetTargetURL($alias,$newRec));
  2306. utopia::AjaxUpdateElement('.'.$enc_name,$data);
  2307. }
  2308. }
  2309. }
  2310. /**
  2311. * List implimentation of uDataModule.
  2312. * Default module for displaying results in a list format. Good for statistics and record searches.
  2313. */
  2314. abstract class uListDataModule extends uDataModule {
  2315. public function UpdateField($fieldAlias,$newValue,&$pkVal = NULL) {
  2316. $isNew = ($pkVal === NULL);
  2317. if (!$this->CheckMaxRows(1)) {
  2318. $this->ResetField($fieldAlias,NULL); // reset the "new record" field
  2319. return;
  2320. }
  2321. $ret = parent::UpdateField($fieldAlias,$newValue,$pkVal);
  2322. if ($pkVal !== NULL && $isNew) {
  2323. $newRec = $this->LookupRecord($pkVal);
  2324. $ov = base64_encode($this->DrawRow($newRec));
  2325. $class = get_class($this);
  2326. AjaxEcho("$('TABLE.$class').children('TBODY').append(Base64.decode('$ov'));\n");
  2327. }
  2328. return $ret;
  2329. }
  2330. public function ResetField($fieldAlias,$pkVal = NULL) {
  2331. // reset the field.
  2332. if ($pkVal && !$this->LookupRecord($pkVal)) {
  2333. $enc_name = $this->GetEncodedFieldName($fieldAlias,$pkVal);
  2334. $this->SendHideRow($pkVal);
  2335. return;
  2336. }
  2337. parent::ResetField($fieldAlias,$pkVal);
  2338. }
  2339. public function DeleteRecord($pkVal) {
  2340. parent::DeleteRecord($pkVal);
  2341. $this->SendHideRow($pkVal);
  2342. $this->CheckMaxRows();
  2343. }
  2344. public function SendHideRow($pk) {
  2345. $rowclass = cbase64_encode(get_class($this).':'.$pk);
  2346. AjaxEcho("$('tr.$rowclass').remove();");
  2347. }
  2348. public function CheckMaxRows($mod = 0) {
  2349. $rows = $this->GetDataset()->CountRecords();
  2350. if (!$this->GetMaxRows() || $rows + $mod < $this->GetMaxRows()) {
  2351. AjaxEcho('$(".newRow").show();');
  2352. return TRUE;
  2353. }
  2354. AjaxEcho('$(".newRow").hide();');
  2355. if ($rows + $mod == $this->GetMaxRows()) return TRUE;
  2356. return FALSE;
  2357. }
  2358. public function GetMaxRows() {
  2359. return NULL;//$this->maxRecs;
  2360. }
  2361. public $limit = 50;
  2362. public function ShowData($dataset = null) {
  2363. echo '<h1>'.$this->GetTitle().'</h1>';
  2364. echo '{list.'.get_class($this).'}';
  2365. array_sort_subkey($this->fields,'order');
  2366. $this->GetLimit($limit,$page);
  2367. if (!$dataset) $dataset = $this->GetDataset();
  2368. $dataset->GetPage($page,$limit);
  2369. $num_rows = $dataset->CountRecords();
  2370. uEvents::TriggerEvent('OnShowDataList',$this);
  2371. // first draw header for list
  2372. if (!isset($GLOBALS['inlineListCount'])) $GLOBALS['inlineListCount'] = 0;
  2373. else $GLOBALS['inlineListCount']++;
  2374. ob_start();
  2375. if (!$this->isAjax) echo '<form class="uf" action="" onsubmit="this.action = window.location" method="post"><input type="hidden" name="__ajax" value="updateField">';
  2376. echo "<div class=\"table-wrapper\"><table class=\"".get_class($this)." layoutListSection module-content\">";
  2377. $sectionFieldTitles = array();
  2378. // TODO: pagination for list record display
  2379. if (!$this->flag_is_set(LIST_HIDE_HEADER)) {
  2380. echo '<thead>';
  2381. ob_start();
  2382. // start of SECTION headers
  2383. if (count($this->layoutSections) > 1) {
  2384. echo '<tr>';
  2385. // need first 'empty' column for buttons?
  2386. if ($this->flag_is_set(ALLOW_DELETE)) { echo "<td>&nbsp;</td>"; }
  2387. $sectionCount = 0;
  2388. $sectionID = NULL;
  2389. $keys = array_keys($this->fields);
  2390. $lastFieldName = end($keys);
  2391. foreach ($this->fields as $fieldName => $fieldData) {
  2392. if ($fieldData['visiblename'] === NULL) continue;
  2393. if ($sectionID === NULL) $sectionID = $fieldData['layoutsection'];
  2394. if ($fieldData['layoutsection'] !== $sectionID) {
  2395. // write the section, and reset the count
  2396. $sectionName = $this->layoutSections[$sectionID]['title'];
  2397. $secClass = empty($sectionName) ? '' : ' sectionHeader';
  2398. echo "<td colspan=\"$sectionCount\" class=\"$secClass\">".nl2br($sectionName)."</td>";
  2399. $sectionCount = 0;
  2400. $sectionID = $fieldData['layoutsection'];
  2401. }
  2402. $sectionFieldTitles[$sectionID] = array_key_exists($sectionID,$sectionFieldTitles) ? $sectionFieldTitles[$sectionID] : !empty($fieldData['visiblename']);
  2403. $sectionCount++;
  2404. }
  2405. $sectionName = $this->layoutSections[$sectionID]['title'];
  2406. $secClass = empty($sectionName) ? '' : ' sectionHeader';
  2407. echo "<td colspan=\"$sectionCount\" class=\"$secClass\">".nl2br($sectionName)."</td>";
  2408. echo "</tr>";
  2409. }
  2410. // start of FIELD headers
  2411. $colcount = 0;
  2412. echo '<tr class="field-headers">';
  2413. if ($this->flag_is_set(ALLOW_DELETE)) { echo '<th"></th>'; $colcount++; }
  2414. foreach ($this->fields as $fieldName => $fieldData) {
  2415. if ($fieldData['visiblename'] === NULL) continue;
  2416. $colcount++;
  2417. // sort?
  2418. $icon = '';
  2419. $o = $this->GetOrderBy(true);
  2420. if (is_array($o)) foreach ($o as $order) {
  2421. if (strpos($order,'`'.$fieldName.'`') !== FALSE) {
  2422. $icon = ' sort-up';
  2423. if (stripos($order,'desc') !== FALSE) $icon = ' sort-down';
  2424. break;
  2425. }
  2426. }
  2427. echo '<th class="field-'.$fieldName.' sortable'.$icon.'" data-field="'.$fieldName.'" data-mid="'.$this->GetModuleId().'">';
  2428. // title
  2429. echo nl2br($fieldData['visiblename']);
  2430. echo "</th>";
  2431. }
  2432. echo '</tr>'; // close column headers
  2433. $header_output = ob_get_clean();
  2434. if ($this->flag_is_set(ALLOW_FILTER) && $this->hasEditableFilters === true && $this->hideFilters !== TRUE) {
  2435. echo '<tr class="noprint"><td class="uFilters" colspan="'.$colcount.'">';
  2436. // other filters
  2437. foreach ($this->filters as $fType) {
  2438. foreach ($fType as $filterset) { //flag_is_set($fieldData['options'],ALLOW_FILTER)) {
  2439. foreach ($filterset as $filterInfo) {
  2440. if ($filterInfo['it'] === itNONE) continue;
  2441. echo $this->GetFilterBox($filterInfo);
  2442. }
  2443. }
  2444. }
  2445. echo '</td></tr>';
  2446. }
  2447. if ($num_rows > 0 || $this->flag_is_set(ALLOW_ADD) || $this->hasEditableFilters === true) echo $header_output;
  2448. echo "</thead>\n";
  2449. }
  2450. // now display data rows
  2451. // process POST filters
  2452. $total = array();
  2453. $totalShown = array();
  2454. timer_start('Draw Rows: '.get_class($this));
  2455. $gUrl = '';
  2456. $body = "<tbody$gUrl>";
  2457. if ($num_rows == 0) {
  2458. } else {
  2459. $i = 0;
  2460. $fields = $this->GetFields();
  2461. while (($row = $dataset->fetch())) {
  2462. $i++;
  2463. // move totals here
  2464. foreach ($fields as $fieldName => $fieldData) {
  2465. switch ($this->GetFieldType($fieldName)) {
  2466. case ftNUMBER:
  2467. case ftCURRENCY:
  2468. case ftPERCENT:
  2469. if (!array_key_exists($fieldName,$total)) $total[$fieldName] = 0;
  2470. if (!array_key_exists($fieldName,$totalShown)) $totalShown[$fieldName] = 0;
  2471. $preProcessValue = floatval(preg_replace('/[^0-9\.-]/','',$this->PreProcess($fieldName,$row[$fieldName],$row)));
  2472. if ($i <= 150) $totalShown[$fieldName] += $preProcessValue;
  2473. $total[$fieldName] += $preProcessValue;
  2474. break;
  2475. default: break;
  2476. }
  2477. }
  2478. $body .= $this->DrawRow($row);
  2479. }
  2480. }
  2481. $body .= "</tbody>";
  2482. timer_end('Draw Rows: '.get_class($this));
  2483. $foot = '';
  2484. $canadd = false;
  2485. foreach ($this->fields as $fieldName => $fieldData) {
  2486. if ($this->flag_is_set(ALLOW_ADD,$fieldName)) {
  2487. $canadd = true;
  2488. break;
  2489. }
  2490. }
  2491. if ($canadd) {
  2492. $hideNew = ($this->GetMaxRows() && $num_rows >= $this->GetMaxRows()) ? ' style="display:none"' : '';
  2493. $foot .= '<tr class="newRow"'.$hideNew.'>';
  2494. if ($this->flag_is_set(ALLOW_DELETE)) $foot .= "<td class=\"new-ident\"></td>";
  2495. foreach ($this->fields as $fieldName => $fieldData) {
  2496. if ($fieldData['visiblename'] === NULL) continue;
  2497. $classes=array();
  2498. $class = count($classes) > 0 ? ' class="'.join(' ',$classes).'"' : '';
  2499. if ($this->flag_is_set(ALLOW_ADD,$fieldName))
  2500. $foot .= "<td$class>".$this->GetCell($fieldName, NULL).'</td>';
  2501. // TODO: Default value not showing on new records (list)
  2502. }
  2503. $foot .= '</tr>';
  2504. }
  2505. if (!empty($total) && $this->flag_is_set(SHOW_TOTALS)) {
  2506. $foot .= '<tr>';
  2507. if ($this->flag_is_set(ALLOW_DELETE)) $foot .= "<td class=\"totals-ident\"></td>";
  2508. foreach ($this->fields as $fieldName => $fieldData) {
  2509. if ($fieldData['visiblename'] === NULL) continue;
  2510. $classes=array();
  2511. $class = count($classes) > 0 ? ' class="'.join(' ',$classes).'"' : '';
  2512. if (array_key_exists($fieldName,$total)) {
  2513. $foot .= "<td$class><b>";
  2514. if ($totalShown[$fieldName] != $total[$fieldName])
  2515. $foot .= htmlentities($this->PreProcess($fieldName,$totalShown[$fieldName])).'(shown)<br/>';
  2516. $foot .= htmlentities($this->PreProcess($fieldName,$total[$fieldName]));
  2517. $foot .= '</b></td>';
  2518. } else
  2519. $foot .= "<td$class></td>";
  2520. }
  2521. $foot .= '</tr>';
  2522. }
  2523. if (!empty($foot)) echo "<tfoot>$foot</tfoot>";
  2524. echo $body;
  2525. // now finish table
  2526. echo "</table></div>";//"</div>";
  2527. if (!$this->isAjax) echo '</form>';
  2528. if ($limit) {
  2529. $pages = max(ceil($num_rows / $limit),1);
  2530. ob_start();
  2531. utopia::OutputPagination($pages,'_p_'.$this->GetModuleId());
  2532. $pagination = ob_get_clean();
  2533. if ($pagination) echo '<div class="pagination right module-content">'.$pagination.'</div>';
  2534. }
  2535. echo '<div class="right module-content">Showing '.(($page*$limit)+1).' - '.min(($page*$limit)+$limit,$num_rows).' of '.$num_rows.'</div>';
  2536. $cont = ob_get_clean();
  2537. echo $cont;
  2538. }
  2539. function DrawRow($row) {
  2540. $pk = $row[$this->GetPrimaryKey()];
  2541. $body = '<tr class="'.cbase64_encode(get_class($this).':'.$pk).'">';
  2542. if ($this->flag_is_set(ALLOW_DELETE)) {
  2543. $delbtn = $this->GetDeleteButton($row[$this->GetPrimaryKey()]);
  2544. $body .= '<td style="width:1px">'.$delbtn.'</td>';
  2545. }
  2546. foreach ($this->fields as $fieldName => $fieldData) {
  2547. if ($fieldData['visiblename'] === NULL) continue;
  2548. $targetUrl = $this->GetTargetUrl($fieldName,$row);
  2549. $body .= '<td>'.$this->GetCell($fieldName,$row,$targetUrl).'</td>';
  2550. }
  2551. $body .= "</tr>\n";
  2552. return $body;
  2553. }
  2554. }
  2555. /**
  2556. * Single form implimentation of uDataModule.
  2557. * Default module for displaying results in a form style. Good for Data Entry.
  2558. */
  2559. abstract class uSingleDataModule extends uDataModule {
  2560. public $itemName = 'Item';
  2561. public function DeleteRecord($pkVal) {
  2562. parent::DeleteRecord($pkVal);
  2563. AjaxEcho('history.go(-1);');
  2564. }
  2565. public function UpdateField($fieldAlias,$newValue,&$pkVal=NULL) {
  2566. $oldPkVal = $pkVal;
  2567. $ret = parent::UpdateField($fieldAlias,$newValue,$pkVal);
  2568. if ($ret === NULL)
  2569. AjaxEcho("window.location.reload(false);");
  2570. elseif ($oldPkVal !== $pkVal) {
  2571. // updated PK
  2572. $filters = $_GET; unset($filters['_n_'.$this->GetModuleId()]);
  2573. $f = $this->FindFilter($this->GetPrimaryKey());
  2574. if ($f) $filters['_f_'.$f['uid']] = $pkVal;
  2575. else $filters[$this->GetPrimaryKey()] = $filters;
  2576. $url = $this->GetURL($filters);
  2577. AjaxEcho("window.location.replace('$url');");
  2578. }
  2579. return $ret;
  2580. }
  2581. public $limit = 1;
  2582. public function ShowData(){
  2583. //check pk and ptable are set up
  2584. if (is_empty($this->GetTabledef())) { ErrorLog('Primary table not set up for '.get_class($this)); return; }
  2585. echo '<h1>'.$this->GetTitle().'</h1>';
  2586. echo '{list.'.get_class($this).'}';
  2587. $row = null;
  2588. $num_rows = 0;
  2589. if (!isset($_GET['_n_'.$this->GetModuleId()])) {
  2590. $this->GetLimit($limit,$page);
  2591. $dataset = $this->GetDataset();
  2592. $num_rows = $dataset->CountRecords();
  2593. $row = $dataset->GetPage($page,$limit)->fetch();
  2594. }
  2595. $pagination = '';
  2596. $this->GetLimit($limit);
  2597. if ($limit) {
  2598. $pages = max(ceil($num_rows / $limit),1);
  2599. ob_start();
  2600. utopia::OutputPagination($pages,'_p_'.$this->GetModuleId());
  2601. $pagination = ob_get_clean();
  2602. }
  2603. $records = ($num_rows == 0) ? "There are no records to display." : 'Total Rows: '.$num_rows;
  2604. $pager = '<div class="right">'.$pagination.'</div>';
  2605. uEvents::TriggerEvent('OnShowDataDetail',$this);
  2606. if ($this->flag_is_set(ALLOW_DELETE) && $row) {
  2607. $fltr =& $this->FindFilter($this->GetPrimaryKey(),ctEQ,itNONE);
  2608. $delbtn = $this->GetDeleteButton($this->GetFilterValue($fltr['uid']),'Delete Record');
  2609. utopia::AppendVar('footer_left',$delbtn);
  2610. }
  2611. // if (!$this->IsNewRecord()) { // records exist, lets get the first.
  2612. // pagination?
  2613. // if (mysql_num_rows($result) > 1) {
  2614. // multiple records exist in this set, sort out pagination
  2615. // }
  2616. // }
  2617. $order = $this->GetSortOrder();
  2618. $extraCount = 1;
  2619. $secCount = count($this->layoutSections);
  2620. foreach ($this->layoutSections as $sectionID => $sectionInfo) {
  2621. $out = '';
  2622. if ($secCount > 1) {
  2623. $sectionName = $sectionInfo['title'];
  2624. if ($sectionName === '') {
  2625. if ($sectionID === 0) $SN = 'General';
  2626. else { $SN = "Extra ($extraCount)"; $extraCount++; }
  2627. } else
  2628. $SN = ucwords($sectionName);
  2629. $out .= '<h2>'.$SN.'</h2>';
  2630. }
  2631. if (!$this->isAjax) $out .= '<form class="uf" action="" onsubmit="this.action = window.location" method="post">';
  2632. $out .= "<div class=\"table-wrapper\"><table class=\"module-content layoutDetailSection\">";
  2633. $fields = $this->GetFields(true,$sectionID);
  2634. $hasFieldHeaders = false;
  2635. foreach ($fields as $fieldName => $fieldData) {
  2636. $hasFieldHeaders = $hasFieldHeaders || !empty($fieldData['visiblename']);
  2637. }
  2638. $fieldCount = count($fields);
  2639. foreach ($fields as $fieldName => $fieldData) {
  2640. $targetUrl = $this->GetTargetUrl($fieldName,$row);
  2641. $out .= "<tr>";
  2642. if ($hasFieldHeaders)
  2643. $out .= "<td class=\"fld\">".$fieldData['visiblename']."</td>";
  2644. $out .= '<td>'.$this->GetCell($fieldName,$row,$targetUrl).'</td>';
  2645. $out .= "</tr>";
  2646. }
  2647. $out .= "</table></div>";
  2648. if (!$this->isAjax) $out .= '</form>';
  2649. echo $out;
  2650. }
  2651. if ($num_rows > 1) echo '<div class="oh"><b>'.$records.'</b>'.$pager.'</div>';
  2652. }
  2653. }