PageRenderTime 60ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/LP/Record.php

https://github.com/radicaldesigns/jaguar
PHP | 564 lines | 466 code | 73 blank | 25 comment | 65 complexity | 705712a24c65c469586d7b24557eba34 MD5 | raw file
Possible License(s): MIT, LGPL-2.1
  1. <?php
  2. require_once('utility.php');
  3. require_once('Data.php');
  4. class Record extends Data {
  5. var $dbcon;
  6. var $itemdata = array( );
  7. var $_itemdata_keys;
  8. var $_allowed_keys = array();
  9. var $id;
  10. var $_sort_property;
  11. var $_sort_direction = 'asc';
  12. var $_sort_method = "";
  13. var $_sort_auto = true;
  14. var $_observers = array( );
  15. var $_collection;
  16. var $_search_criteria_global = array( );
  17. var $_field_status = 'publish';
  18. var $_field_listorder = 'listorder';
  19. var $_exact_value_fields = array( );
  20. var $_allow_db_cache = false;
  21. //flag for actions based on whether a bulk request is being executed
  22. var $list_action;
  23. /* BEGIN ActiveRecord port */
  24. var $name_field;
  25. protected static $schema;
  26. protected static $schema_json;
  27. /* END ActiveRecord port */
  28. protected static $db;
  29. function __construct( $item_id = null ) {
  30. $this->dbcon = self::getDb();
  31. $this->setSource( $this->datatable );
  32. if (isset($item_id) && $item_id) $this->readData( $item_id );
  33. }
  34. static function getDb(){
  35. return self::$db;
  36. }
  37. static function setDb($dbcon){
  38. self::$db = $dbcon;
  39. }
  40. /* BEGIN ActiveRecord port */
  41. function get($field = null){
  42. $s = $this::_getSchema(get_class($this));
  43. if ( !empty($s['values']) && !empty($s['values'][$field])) return $s['values'][$field][$this->getData($field)];
  44. return $this->getData($field);
  45. }
  46. function getName(){
  47. return $this->getData($this->name_field);
  48. }
  49. function destroy(){
  50. return $this->delete();
  51. }
  52. static function getOne( $search_criteria){
  53. $class = get_called_class();
  54. $finder = new $class();
  55. $objects = $finder->find( $search_criteria);
  56. if(is_array($objects)) return array_shift($objects);
  57. }
  58. static function getMany( $search_criteria){
  59. $class = get_called_class();
  60. if(empty($search_criteria)) return self::getAll();
  61. foreach($search_criteria as $key=>$value) if( empty($value)) unset( $search_criteria[$key]);
  62. $finder = new $class();
  63. $objects = $finder->find( $search_criteria);
  64. if(!$objects) $objects = array();
  65. return $objects;
  66. }
  67. static function getAll(){
  68. $class = get_called_class();
  69. if(!class_exists($class)) bail("Class $class does not exist");
  70. $finder = new $class;
  71. $objects = $finder->find( array());
  72. if(!$objects) $objects = array();
  73. return $objects;
  74. }
  75. function getFieldType( $field){
  76. $s = $this::_getSchema(get_class($this));
  77. if ( !array_key_exists( $field, $s['fields'])) bail("db field '$field' does not exist in schema file for ".get_class($this).".");
  78. if ( !$s['fields'][$field]) bail( "db field '$field' exists in schema file, but does not have it's type set");
  79. if ( !empty($s['values']) && !empty($s['values'][$field])) return $s['values'][$field];
  80. return $s['fields'][$field];
  81. }
  82. private static function _getSchema($class=''){
  83. if ( !isset( $class::$schema)) {
  84. $class::$schema = json_decode( $class::$schema_json);
  85. }
  86. return $class::$schema;
  87. }
  88. function getHistory($o = array()){
  89. if(!empty($o['types'])){
  90. $types = array_intersect($o['types'],$this->history_types);
  91. unset($o['types']);
  92. } else {
  93. $types = $this->history_types;
  94. }
  95. $history = array();
  96. foreach( $types as $type){
  97. $method_name = 'get'.$type;
  98. if( !method_exists($this,$method_name)) bail('method '.get_class($this)."::$method_name not found");
  99. $history_objects = $this->$method_name();
  100. if(!$history_objects) continue;
  101. $history = array_merge( $history, $history_objects);
  102. }
  103. usort( $history, array($this,'compareHistoryDates'));
  104. return $history;
  105. }
  106. function compareHistoryDates( $a, $b){
  107. $date_a = $a->getHistoryDate();
  108. $date_b = $b->getHistoryDate();
  109. if($date_a == $date_b) return 0;
  110. return ($date_a > $date_b) ? -1 : 1;
  111. }
  112. function getHistoryType(){
  113. return get_class($this);
  114. }
  115. function defaultSearchCriteria( $field_name){}
  116. function makeCriteriaDateRange( $data ) {
  117. $field_name = isset($data['field_name']) ? $data['field_name'] : 'date';
  118. if(isset($data['start_date']) && isset($data['end_date'])
  119. && $data['start_date'] && $data['end_date'] ) {
  120. return "$field_name >= " . $this->dbcon->qstr($data['start_date'])
  121. . " AND $field_name <= " . $this->dbcon->qstr($data['end_date']);
  122. } elseif(isset($data['start_date']) && $data['start_date'] ) {
  123. return "$field_name >= " . $this->dbcon->qstr($data['start_date']);
  124. } elseif(isset($data['end_date']) && $data['end_date'] ) {
  125. return "$field_name <= " . $this->dbcon->qstr($data['end_date']);
  126. }
  127. return;
  128. }
  129. function makeCriteriaCurrentMonth($date_field){
  130. if( !$date_field || $date_field === true) $date_field = 'date';
  131. $start_date = Util::date_format_from_time(Util::start_of_current_month());
  132. $end_date = Util::date_format_from_time(Util::end_of_current_month());
  133. $criteria = array(
  134. 'field_name'=>$date_field,
  135. 'start_date'=>$start_date,
  136. 'end_date'=>$end_date
  137. );
  138. return $this->makeCriteriaDateRange( $criteria );
  139. }
  140. function makeCriteriaCurrentWeek($date_field){
  141. if( !$date_field || $date_field === true) $date_field = 'date';
  142. $start_date = Util::date_format_from_time(Util::start_of_current_week());
  143. $end_date = Util::date_format_from_time(Util::end_of_current_week());
  144. $criteria = array(
  145. 'field_name'=>$date_field,
  146. 'start_date'=>$start_date,
  147. 'end_date'=>$end_date
  148. );
  149. return $this->makeCriteriaDateRange( $criteria );
  150. }
  151. protected function _makeCriteriaMultiple($fieldname, $values) {
  152. if(empty($values)) return;
  153. if(is_array($values)) {
  154. return "$fieldname IN (". implode(",", $values). ")";
  155. }
  156. return "$fieldname = " . $this->dbcon->qstr( $values );
  157. }
  158. function isValid() {
  159. //stub
  160. return true;
  161. }
  162. function destroyAssociatedRecords(){
  163. // overwrite in subclass
  164. }
  165. /* END ActiveRecord port*/
  166. function read( $item_id ) {
  167. return $this->readData( $item_id );
  168. }
  169. function cloneRecord(){
  170. $obj = clone $this;
  171. $obj->_itemdata_keys[ $this->id_field ] = null;
  172. $obj->id = null;
  173. return $obj;
  174. }
  175. function setSource( $sourcename = null ) {
  176. parent::setSource( $sourcename );
  177. $this->_itemdata_keys = $this->_getColumnNames( $this->datatable );
  178. if(!is_array($this->_itemdata_keys)) bail('This model has no collumns in the database. Check to see if you ran the latest migrations, and make sure you set the correct datatable name on the model');
  179. $this->_allowed_keys = $this->_itemdata_keys;
  180. }
  181. function _addAllowedKey( $key_name ) {
  182. if (array_search( $key_name, $this->_allowed_keys )!==FALSE) return true;
  183. $this->_allowed_keys[] = $key_name;
  184. }
  185. function getAllowedKeys( ) {
  186. return $this->_allowed_keys;
  187. }
  188. function dropID( ){
  189. unset ( $this->_itemdata_keys[ $this->id_field ] );
  190. unset ( $this->id );
  191. }
  192. function addCriteriaId( $item_id ){
  193. $this->addCriteria( $this->id_field." = ".$this->dbcon->qstr( $item_id ) );
  194. }
  195. function addCriteriaGlobal( $criteria ){
  196. $this->_search_criteria_global = array_merge( $this->_search_criteria_global, $criteria );
  197. }
  198. function readData ( $item_id ) {
  199. if ( $item_id !== FALSE ) $this->addCriteriaId( $item_id );
  200. $sql = $this->_assembleSQL();
  201. AMP::debug_sql($sql, get_class($this));
  202. if ( $itemdata = $this->dbcon->GetRow( $sql )) {
  203. $this->set_data_from_db($itemdata);
  204. return true;
  205. }
  206. if ($this->dbcon->ErrorMsg() )
  207. trigger_error ( sprintf( AMP_TEXT_ERROR_DATABASE_READ_FAILED, get_class( $this ) , $this->dbcon->ErrorMsg() ));
  208. return false;
  209. }
  210. function set_data_from_db($data) {
  211. $this->itemdata = $data;
  212. $this->id = $data[ $this->id_field ];
  213. }
  214. function hasData() {
  215. return (isset( $this->itemdata) && !empty($this->itemdata));
  216. }
  217. function deleteById( $item_id ) {
  218. return $this->deleteData( $item_id );
  219. }
  220. function deleteByCriteria( $criteria ) {
  221. $sql_criteria_set = $this->makeCriteria( $criteria );
  222. if ( !$sql_criteria_set || empty( $sql_criteria_set ) ) {
  223. return false;
  224. }
  225. $sql_criteria = ' WHERE ' . join( " AND ", $sql_criteria_set ) ;
  226. $sql = "Delete from " . $this->datatable . $sql_criteria;
  227. if ( ( $itemdata = $this->dbcon->Execute( $sql )) && $this->dbcon->Affected_Rows( )) {
  228. return true;
  229. }
  230. trigger_error ( AMP_TEXT_DELETE . ' ' . sprintf( AMP_TEXT_ERROR_DATABASE_SAVE_FAILED, get_class( $this ), $this->dbcon->ErrorMsg() . ' // ' . $sql ));
  231. return false ;
  232. }
  233. function deleteData( $item_id ) {
  234. $sql = "Delete from " . $this->datatable . " where ". $this->id_field ." = ". $this->dbcon->qstr( $item_id );
  235. if ( ( $itemdata = $this->dbcon->Execute( $sql )) && $this->dbcon->Affected_Rows( )) {
  236. $cached_sql = $this->_assembleSqlByID( $item_id );
  237. $this->dbcon->CacheFlush( $cached_sql ) ;
  238. AMP::debug_sql($sql, get_class($this)." cleared cache");
  239. return true;
  240. }
  241. trigger_error ( AMP_TEXT_DELETE . sprintf( AMP_TEXT_ERROR_DATABASE_SAVE_FAILED, get_class( $this ), $this->dbcon->ErrorMsg() ));
  242. return false ;
  243. }
  244. function delete( ){
  245. if ( !isset( $this->id )) return false;
  246. if ( !$this->deleteData( $this->id )) return false;
  247. return true;
  248. }
  249. function _assembleSqlByID( $id ) {
  250. return $this->_makeSelect().
  251. $this->_makeSource().
  252. " WHERE ".$this->id_field." = ". $this->dbcon->qstr( $id );
  253. }
  254. function save() {
  255. $item_data = $this->get( );
  256. $save_fields = AMP::array_filter_by_keys($this->_itemdata_keys, $item_data );
  257. if ( !is_array( $this->id_field ) && !isset( $save_fields[ $this->id_field ] )) {
  258. $save_fields[ $this->id_field ] = "";
  259. }
  260. $result = $this->dbcon->Replace( $this->datatable, $save_fields, $this->id_field, $quote=true);
  261. if ($result == 2 ) {
  262. $this->set( array( $this->id_field => $this->dbcon->Insert_ID() ));
  263. $this->id = $this->get( $this->id_field );
  264. }
  265. if ($result) {
  266. $this->clearItemCache( $this->id );
  267. return true;
  268. }
  269. $this->addError( sprintf( AMP_TEXT_ERROR_DATABASE_SAVE_FAILED, get_class( $this ), AMP_TEXT_ERROR_DATABASE_PROBLEM ) );
  270. trigger_error ( sprintf( AMP_TEXT_ERROR_DATABASE_SAVE_FAILED, get_class( $this ), $this->dbcon->ErrorMsg() ));
  271. return false;
  272. }
  273. function clearItemCache( $id ) {
  274. $sql = $this->_assembleSqlByID( $id );
  275. $this->dbcon->CacheFlush( $sql );
  276. AMP::debug_sql($sql, get_class($this)." cleared cache");
  277. $data_set = &$this->getCollection( );
  278. $data_set->clearCache( );
  279. }
  280. function clear_cache( ){
  281. if ( isset( $this->id )) {
  282. return $this->clearItemCache( $this->id );
  283. }
  284. }
  285. function mergeData( $data ) {
  286. $this->set($data);
  287. }
  288. function setData( $data ) {
  289. $this->set( $data );
  290. }
  291. function set( $data){
  292. if(!$this->itemdata) $this->itemdata= array();
  293. $data = AMP::array_filter_by_keys( $this->_allowed_keys, $data);
  294. $this->itemdata = array_merge( $this->itemdata, $data);
  295. if (isset($data[$this->id_field]) && $data[$this->id_field]) $this->id = $data[$this->id_field];
  296. }
  297. function getData( $fieldname = null ) {
  298. if (!isset($fieldname)) return $this->itemdata;
  299. if (isset($this->itemdata[$fieldname])){
  300. return $this->itemdata[$fieldname];
  301. }
  302. return false;
  303. }
  304. function legacyFieldname( $data, $oldname, $newname ) {
  305. if (isset($data[$oldname])) $this->itemdata[$newname] = $data[$oldname];
  306. if (isset($data[$newname])) {
  307. $this->itemdata[$newname] = $data[$newname];
  308. $this->itemdata[$oldname] = $data[$newname];
  309. }
  310. $this->_addAllowedKey($newname);
  311. }
  312. /**
  313. * Search methods
  314. *
  315. * @param mixed $criteria
  316. * @param mixed $class_name
  317. * @access public
  318. * @return void
  319. */
  320. function find( $criteria = null ) {
  321. $class_name = get_class($this);
  322. $collection = $this->getCollection( $this->makeCriteria( $criteria ));
  323. $collection->setSortAndLimit( $criteria );
  324. if ( !$collection->readData( )) return false;
  325. $objects = $collection->instantiateItems( $collection->getArray( ), $class_name );
  326. if ( empty( $objects )) return $objects;
  327. if ( $this->_sort_auto && !$collection->getSort( )) $this->sort( $objects );
  328. return $objects;
  329. }
  330. function getCollection( $criteria = null ){
  331. if ( isset( $this->_collection ) && $this->_collection ) {
  332. if ( !isset( $criteria )) return $this->_collection;
  333. $collection = &$this->_collection;
  334. } else {
  335. $collection = new RecordCollection( $this->dbcon );
  336. $collection->setSource( $this->datatable );
  337. }
  338. if ( isset( $criteria )) {
  339. $collection->setCriteria( $criteria );
  340. }
  341. foreach( $this->_search_criteria_global as $crit_phrase ){
  342. $collection->addCriteria( $crit_phrase );
  343. }
  344. if ( !$this->_allow_db_cache ) $collection->clearCache( );
  345. $this->_collection = &$collection;
  346. return $this->_collection;
  347. }
  348. //}}}
  349. //{{{ Sorting methods: sort, setSortMethod, _sort_default
  350. function sort( &$item_set, $sort_property=null, $sort_direction = null ){
  351. if ( !( isset( $sort_property) && $sort_property )) {
  352. $this->_sort_default( $item_set );
  353. return true;
  354. }
  355. if ( !$this->setSortMethod( $sort_property )) {
  356. trigger_error( sprintf( AMP_TEXT_ERROR_SORT_PROPERTY_FAILED, $sort_property, get_class( $this )));
  357. return false;
  358. }
  359. if ( isset( $sort_direction )) $this->_sort_direction = $sort_direction;
  360. uasort( $item_set, array( $this, '_sort_compare' ));
  361. return true;
  362. }
  363. function _sort_compare( $file1, $file2 ) {
  364. if ( !( $sort_method = $this->_sort_accessor )) return 0;
  365. if ( !is_object( $file2)) {
  366. return 0;
  367. }
  368. //sort descending
  369. if ( strtolower( $this->_sort_direction ) == 'desc') {
  370. return strnatcasecmp( $file2->$sort_method( ) ,
  371. $file1->$sort_method( ) );
  372. }
  373. //sort ascending
  374. return strnatcasecmp( $file1->$sort_method( ) , $file2->$sort_method( ) );
  375. }
  376. function setSortMethod( $sort_property ) {
  377. $access_method = 'get' . AMP::camelcase( $sort_property );
  378. if ( !method_exists( $this, $access_method )) return false;
  379. $this->_sort_accessor = $access_method;
  380. return true;
  381. }
  382. function _sort_default( &$item_set ){
  383. return $this->sort( $item_set, 'name');
  384. }
  385. //}}}
  386. //{{{ Object based Search methods: makeCriteria
  387. /**
  388. * makeCriteria
  389. *
  390. * @param mixed $data
  391. * @access public
  392. * @return void
  393. */
  394. function makeCriteria( $data ){
  395. $return = array( );
  396. if ( !( isset( $data ) && is_array( $data ))) return false;
  397. foreach ($data as $key => $value) {
  398. $crit_method1 = 'makeCriteria' . ucfirst( $key );
  399. $crit_method2 = 'makeCriteria' . AMP::camelcase( $key );
  400. $crit_method = ( method_exists( $this, $crit_method1)) ? $crit_method1 : $crit_method2;
  401. if (method_exists( $this, $crit_method )) {
  402. $return[$key] = $this->$crit_method( $value );
  403. continue;
  404. }
  405. if ( $crit_method = $this->_getCriteriaMethod( $key, $value )){
  406. $return[$key] = $this->$crit_method( $key, $value );
  407. }
  408. }
  409. return array_filter( $return );
  410. }
  411. function _getCriteriaMethod( $fieldname, $value ) {
  412. if ( !$this->isColumn( $fieldname )) return false;
  413. if (array_search( $fieldname, $this->_exact_value_fields ) !==FALSE) return '_makeCriteriaEquals';
  414. if ( is_numeric( $value )) return '_makeCriteriaEquals';
  415. return '_makeCriteriaContains';
  416. }
  417. function _makeCriteriaContains( $key, $value ) {
  418. return $key . ' LIKE ' . $this->dbcon->qstr( '%' . $value . '%' );
  419. }
  420. function _makeCriteriaEquals( $key, $value ) {
  421. return $key . ' = ' . $this->dbcon->qstr( $value );
  422. }
  423. function makeCriteriaId( $value ) {
  424. if ( !$value ) return 'TRUE';
  425. if ( !is_array( $value )) return $this->_makeCriteriaEquals( 'id', $value );
  426. return 'id in ( '.join( ',', $value).')';
  427. }
  428. function makeCriteriaNotId( $ids ) {
  429. if ( !$ids ) return "TRUE";
  430. if ( !( is_array( $ids ))) return 'id != '.$ids;
  431. return "id not in ( " . join(",", $ids ) . ")";
  432. }
  433. function create( $attributes = array( ), $class_name = null ) {
  434. if( !$class_name ) {
  435. $context = debug_backtrace( );
  436. trigger_error( 'class name not included for call to '.__FUNCTION__.' on '.$debug_backtrace[0]['class']);
  437. return false;
  438. }
  439. $item = new $class_name( AMP::getDb( ));
  440. $item->mergeData( $attributes );
  441. return $item;
  442. }
  443. function update_all( $action, $criteria = array( )){
  444. if( !is_array( $action )) {
  445. $action = array( $action );
  446. }
  447. $search = &$this->getCollection( );
  448. if( is_array( $criteria )) {
  449. $scope = join( ' AND ', $this->makeCriteria( $criteria )) ;
  450. } else {
  451. $scope = $criteria;
  452. }
  453. return $search->updateData( $action, $scope );
  454. }
  455. }