Model.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. <?php if ( ! defined( 'ABSPATH' ) ) exit;
  2. /**
  3. * Class NF_Abstracts_Model
  4. */
  5. class NF_Abstracts_Model
  6. {
  7. /**
  8. * Database Object
  9. *
  10. * @var string
  11. */
  12. protected $_db = '';
  13. /**
  14. * ID
  15. *
  16. * The ID is assigned after being saved to the database.
  17. *
  18. * @var int
  19. */
  20. protected $_id = '';
  21. /**
  22. * Temporary ID
  23. *
  24. * The temporary ID is used to reference unsaved objects
  25. * before they are stored in the database.
  26. *
  27. * @var string
  28. */
  29. protected $_tmp_id = '';
  30. /**
  31. * Type
  32. *
  33. * The type is used to pragmatically identify the type
  34. * of an object without inspecting the class.
  35. *
  36. * @var string
  37. */
  38. protected $_type = '';
  39. /**
  40. * Parent ID
  41. *
  42. * The ID of the parent object.
  43. *
  44. * @var string
  45. */
  46. protected $_parent_id = '';
  47. /**
  48. * Parent Type
  49. *
  50. * The type of the parent object.
  51. *
  52. * @var string
  53. */
  54. protected $_parent_type = '';
  55. /**
  56. * Table Name
  57. *
  58. * The name of the table where the model objects are stored.
  59. *
  60. * @var string
  61. */
  62. protected $_table_name = '';
  63. /**
  64. * Meta Table Name
  65. *
  66. * The name of the table where the object settings are stored.
  67. *
  68. * @var string
  69. */
  70. protected $_meta_table_name = '';
  71. /**
  72. * ? Deprecated ?
  73. * @var string
  74. */
  75. protected $_relationships_table = 'nf3_relationships';
  76. /**
  77. * Columns
  78. *
  79. * A list of settings that are stored in the main table as columns.
  80. * These settings are NOT stored in the meta table.
  81. *
  82. * @var array
  83. */
  84. protected $_columns = array();
  85. /**
  86. * Settings
  87. *
  88. * A list of settings that are stored in the meta table.
  89. *
  90. * @var array
  91. */
  92. protected $_settings = array();
  93. /**
  94. * Results
  95. *
  96. * The last results returned by a query.
  97. *
  98. * @var array
  99. */
  100. protected $_results = array();
  101. /**
  102. * Cache
  103. *
  104. * A Flag for using or bypassing caching.
  105. *
  106. * @var bool
  107. */
  108. protected $_cache = TRUE;
  109. //-----------------------------------------------------
  110. // Public Methods
  111. //-----------------------------------------------------
  112. /**
  113. * NF_Abstracts_Model constructor.
  114. *
  115. * @param $db
  116. * @param $id
  117. * @param $parent_id
  118. */
  119. public function __construct( $db, $id = NULL, $parent_id = '' )
  120. {
  121. /*
  122. * Injected the Database Dependency
  123. */
  124. $this->_db = $db;
  125. /*
  126. * Assign Database Tables using the DB prefix
  127. */
  128. $this->_table_name = $this->_db->prefix . $this->_table_name;
  129. $this->_meta_table_name = $this->_db->prefix . $this->_meta_table_name;
  130. $this->_relationships_table = $this->_db->prefix . $this->_relationships_table;
  131. /*
  132. * Set the object ID
  133. * Check if the ID is Permanent (int) or Temporary (string)
  134. */
  135. if( is_numeric( $id ) ) {
  136. $this->_id = absint( $id );
  137. } elseif( $id ) {
  138. $this->_tmp_id = $id;
  139. }
  140. /*
  141. * Set the Parent ID for context
  142. */
  143. $this->_parent_id = $parent_id;
  144. }
  145. /**
  146. * Get the Permanent ID
  147. *
  148. * @return int
  149. */
  150. public function get_id()
  151. {
  152. return intval( $this->_id );
  153. }
  154. /**
  155. * Get the Temporary ID
  156. *
  157. * @return null|string
  158. */
  159. public function get_tmp_id()
  160. {
  161. return $this->_tmp_id;
  162. }
  163. /**
  164. * Get the Type
  165. *
  166. * @return string
  167. */
  168. public function get_type()
  169. {
  170. return $this->_type;
  171. }
  172. /**
  173. * Get a single setting with a default fallback
  174. *
  175. * @param string $setting
  176. * @param bool $default optional
  177. * @return string|int|bool
  178. */
  179. public function get_setting( $setting, $default = FALSE )
  180. {
  181. if( isset( $this->_settings[ $setting ] )){
  182. $return = $this->_settings[ $setting ];
  183. } else {
  184. $return = $this->get_settings($setting);
  185. if( is_array( $return ) && empty( $return ) ) $return = false;
  186. }
  187. return ( $return !== false ) ? $return : $default;
  188. }
  189. /**
  190. * Get Settings
  191. *
  192. * @param string ...$only returns a subset of the object's settings
  193. * @return array
  194. */
  195. public function get_settings()
  196. {
  197. // If the ID is not set, then we cannot pull settings from the Database.
  198. if( ! $this->_id ) return $this->_settings;
  199. if( ! $this->_settings && 'field' == $this->_type ) {
  200. global $wpdb;
  201. $results = $wpdb->get_results(
  202. "
  203. SELECT Meta.key, Meta.value
  204. FROM $this->_table_name as Object
  205. JOIN $this->_meta_table_name as Meta
  206. ON Object.id = Meta.parent_id
  207. WHERE Object.id = '$this->_id'
  208. "
  209. , ARRAY_A );
  210. foreach( $results as $result ) {
  211. $key = $result[ 'key' ];
  212. $this->_settings[ $key ] = $result[ 'value' ];
  213. }
  214. $field = $wpdb->get_row(
  215. "
  216. SELECT `label`, `key`, `type`
  217. FROM $this->_table_name
  218. WHERE `id` = '$this->_id'
  219. ",
  220. ARRAY_A
  221. );
  222. if( ! is_wp_error( $field ) ){
  223. $this->_settings[ 'label' ] = $field[ 'label' ];
  224. $this->_settings[ 'key' ] = $field[ 'key' ];
  225. $this->_settings[ 'type' ] = $field[ 'type' ];
  226. }
  227. }
  228. if( ! $this->_settings ) {
  229. $form_cache = WPN_Helper::get_nf_cache( $this->_parent_id );
  230. if ($form_cache) {
  231. if ('field' == $this->_type) {
  232. if (isset($form_cache['fields'])) {
  233. foreach ($form_cache['fields'] as $object) {
  234. if ($this->_id != $object['id']) continue;
  235. $this->update_settings($object['settings']);
  236. break;
  237. }
  238. }
  239. }
  240. }
  241. }
  242. // Only query if settings haven't been already queried or cache is FALSE.
  243. if( ! $this->_settings || ! $this->_cache ) {
  244. // Build query syntax from the columns property.
  245. $columns = '`' . implode( '`, `', $this->_columns ) . '`';
  246. // Query column settings
  247. $results = $this->_db->get_row(
  248. "
  249. SELECT $columns
  250. FROM `$this->_table_name`
  251. WHERE `id` = $this->_id
  252. "
  253. );
  254. /*
  255. * If the query returns results then
  256. * assign settings using the column name as the setting key.
  257. */
  258. if( $results ) {
  259. foreach ($this->_columns as $column) {
  260. $this->_settings[$column] = $results->$column;
  261. }
  262. }
  263. $meta_select_fields = "SELECT `key`, `value`";//, `meta_key`,
  264. //`meta_value`";
  265. // Query settings from the meta table.
  266. $meta_results = $this->_db->get_results(
  267. $meta_select_fields .
  268. "
  269. FROM `$this->_meta_table_name`
  270. WHERE `parent_id` = $this->_id
  271. "
  272. );
  273. // Assign settings to the settings property.
  274. foreach ($meta_results as $meta) {
  275. // If we don't already have a value from the main table...
  276. // OR If that value was NULL...
  277. if ( ! isset( $this->_settings[ $meta->key ] ) || NULL == $this->_settings[ $meta->key ] ) {
  278. // TODO: Update this logic after removal of original meta columns.
  279. // Set the value from meta.
  280. $this->_settings[ $meta->key ] = $meta->value;
  281. }
  282. }
  283. }
  284. // Un-serialize queried settings results.
  285. foreach( $this->_settings as $key => $value ){
  286. $this->_settings[ $key ] = maybe_unserialize( $value );
  287. }
  288. // Check for passed arguments to limit the returned settings.
  289. $only = func_get_args();
  290. if ( $only && is_array($only)
  291. // And if the array is NOT multidimensional
  292. && (count($only) == count($only, COUNT_RECURSIVE))) {
  293. // If only one setting, return a single value
  294. if( 1 == count( $only ) ){
  295. if( isset( $this->_settings[ $only[0] ] ) ) {
  296. return $this->_settings[$only[0]];
  297. } else {
  298. return NULL;
  299. }
  300. }
  301. // Flip the array to match the settings property
  302. $only_settings = array_flip( $only );
  303. // Return only the requested settings
  304. return array_intersect_key( $this->_settings, $only_settings );
  305. }
  306. // Return all settings
  307. return $this->_settings;
  308. }
  309. /**
  310. * Update Setting
  311. *
  312. * @param $key
  313. * @param $value
  314. * @return bool|false|int
  315. */
  316. public function update_setting( $key, $value )
  317. {
  318. $this->_settings[ $key ] = $value;
  319. return $this;
  320. }
  321. /**
  322. * Update Settings
  323. *
  324. * @param $data
  325. * @return bool
  326. */
  327. public function update_settings( $data )
  328. {
  329. if( is_array( $data ) ) {
  330. foreach ($data as $key => $value) {
  331. $this->update_setting($key, $value);
  332. }
  333. }
  334. return $this;
  335. }
  336. /**
  337. * Delete
  338. *
  339. * Delete the object, its children, and its relationships.
  340. *
  341. * @return bool
  342. */
  343. public function delete()
  344. {
  345. if( ! $this->get_id() ) return;
  346. $results = array();
  347. // Delete the object from the model's table
  348. $results[] = $this->_db->delete(
  349. $this->_table_name,
  350. array(
  351. 'id' => $this->_id
  352. )
  353. );
  354. // Delete settings from the model's meta table.
  355. $results[] = $this->_db->delete(
  356. $this->_meta_table_name,
  357. array(
  358. 'parent_id' => $this->_id
  359. )
  360. );
  361. // Query for child objects using the relationships table.
  362. $children = $this->_db->get_results(
  363. "
  364. SELECT child_id, child_type
  365. FROM $this->_relationships_table
  366. WHERE parent_id = $this->_id
  367. AND parent_type = '$this->_type'
  368. "
  369. );
  370. // Delete each child model
  371. foreach( $children as $child ) {
  372. $model = Ninja_Forms()->form()->get_model( $child->child_id, $child->child_type );
  373. $model->delete();
  374. }
  375. // Delete all relationships
  376. $this->_db->delete(
  377. $this->_relationships_table,
  378. array(
  379. 'parent_id' => $this->_id,
  380. 'parent_type' => $this->_type
  381. )
  382. );
  383. // return False if there are no query errors.
  384. return in_array( FALSE, $results );
  385. }
  386. /**
  387. * Find
  388. *
  389. * @param string $parent_id
  390. * @param array $where
  391. * @return array
  392. */
  393. public function find( $parent_id = '', array $where = array() )
  394. {
  395. // Build the query using the $where argument
  396. $query = $this->build_meta_query( $parent_id, $where );
  397. // Get object IDs from the query
  398. $ids = $this->_db->get_col( $query );
  399. // Get the current class name
  400. $class = get_class( $this );
  401. $results = array();
  402. foreach( $ids as $id ){
  403. // Instantiate a new object for each ID
  404. $results[] = $object = new $class( $this->_db, $id, $parent_id );
  405. }
  406. // Return an array of objects
  407. return $results;
  408. }
  409. /*
  410. * UTILITY METHODS
  411. */
  412. /**
  413. * Save
  414. */
  415. public function save()
  416. {
  417. $data = array ( 'updated_at' => current_time( 'mysql' ));
  418. // If the ID is not set, assign an ID
  419. if( ! $this->_id ){
  420. $data[ 'created_at' ] = current_time( 'mysql' ) ;
  421. if( $this->_parent_id ){
  422. $data['parent_id'] = $this->_parent_id;
  423. }
  424. // Create a new row in the database
  425. $result = $this->_db->insert(
  426. $this->_table_name,
  427. $data
  428. );
  429. // Assign the New ID
  430. $this->_id = $this->_db->insert_id;
  431. } else {
  432. $result = $this->_db->get_row( "SELECT * FROM $this->_table_name WHERE id = $this->_id" );
  433. if( ! $result ){
  434. $this->_insert_row( array( 'id' => $this->_id ) );
  435. }
  436. }
  437. $this->_save_settings();
  438. // If a Temporary ID is set, return it along with the newly assigned ID.
  439. if( $this->_tmp_id ){
  440. return array( $this->_tmp_id => $this->_id );
  441. }
  442. }
  443. public function _insert_row( $data = array() )
  444. {
  445. $data[ 'created_at' ] = current_time( 'mysql' );
  446. if( $this->_parent_id ){
  447. $data['parent_id'] = $this->_parent_id;
  448. }
  449. // Create a new row in the database
  450. $result = $this->_db->insert(
  451. $this->_table_name,
  452. $data
  453. );
  454. }
  455. /**
  456. * Cache Flag
  457. *
  458. * @param string $cache
  459. * @return $this
  460. */
  461. public function cache( $cache = '' )
  462. {
  463. // Set the Cache Flag Property.
  464. if( $cache !== '' ) {
  465. $this->_cache = $cache;
  466. }
  467. // Return the current object for method chaining.
  468. return $this;
  469. }
  470. /**
  471. * Add Parent
  472. *
  473. * Set the Parent ID and Parent Type properties
  474. *
  475. * @param $parent_id
  476. * @param $parent_type
  477. * @return $this
  478. */
  479. public function add_parent( $parent_id, $parent_type )
  480. {
  481. $this->_parent_id = $parent_id;
  482. $this->_parent_type = $parent_type;
  483. // Return the current object for method chaining.
  484. return $this;
  485. }
  486. //-----------------------------------------------------
  487. // Protected Methods
  488. //-----------------------------------------------------
  489. /**
  490. * Save Setting
  491. *
  492. * Save a single setting.
  493. *
  494. * @param $key
  495. * @param $value
  496. * @return bool|false|int
  497. */
  498. protected function _save_setting( $key, $value )
  499. {
  500. // If the setting is a column, save the settings to the model's table.
  501. if( in_array( $key, $this->_columns ) ){
  502. $format = null;
  503. if( in_array( $key, array( 'show_title', 'clear_complete', 'hide_complete', 'logged_in' ) ) ) {
  504. // gotta set the format for the columns that use bit type
  505. $format = '%d';
  506. }
  507. if( 'form' === $this->_type && 'title' == $key ) {
  508. $this->_db->update(
  509. $this->_table_name,
  510. array(
  511. 'form_title' => $value
  512. ),
  513. array(
  514. 'id' => $this->_id
  515. ),
  516. $format
  517. );
  518. }
  519. // Don't update the form_title. Duplicating issue for now
  520. if( 'form_title' !== $key ) {
  521. $update_model = $this->_db->update(
  522. $this->_table_name,
  523. array(
  524. $key => $value
  525. ),
  526. array(
  527. 'id' => $this->_id
  528. ),
  529. $format
  530. );
  531. } else {
  532. return 1;
  533. }
  534. /*
  535. * if it's not a form, you can return, but we are still saving some
  536. * settings for forms in the form_meta table
  537. */
  538. if( 'form' != $this->_type
  539. || ( 'form' == $this->_type && 'title' == $key ) ) {
  540. return $update_model;
  541. }
  542. }
  543. $meta_row = $this->_db->get_row(
  544. "
  545. SELECT `value`
  546. FROM `$this->_meta_table_name`
  547. WHERE `parent_id` = $this->_id
  548. AND `key` = '$key'
  549. "
  550. );
  551. if( $meta_row ){
  552. $update_values = array(
  553. 'value' => $value
  554. );
  555. // for forms we need to update the meta_key and meta_value columns
  556. if( 'form' == $this->_type ) {
  557. $update_values[ 'meta_key' ] = $key;
  558. $update_values[ 'meta_value' ] = $value;
  559. }
  560. $result = $this->_db->update(
  561. $this->_meta_table_name,
  562. $update_values,
  563. array(
  564. 'key' => $key,
  565. 'parent_id' => $this->_id
  566. )
  567. );
  568. } else {
  569. $insert_values = array(
  570. 'key' => $key,
  571. 'value' => $value,
  572. 'parent_id' => $this->_id
  573. );
  574. // for forms we need to update the meta_key and meta_value columns
  575. if( 'form' == $this->_type ) {
  576. $insert_values[ 'meta_key' ] = $key;
  577. $insert_values[ 'meta_value' ] = $value;
  578. }
  579. $result = $this->_db->insert(
  580. $this->_meta_table_name,
  581. $insert_values,
  582. array(
  583. '%s',
  584. '%s',
  585. '%d'
  586. )
  587. );
  588. }
  589. return $result;
  590. }
  591. /**
  592. * Save Settings
  593. *
  594. * Save all settings.
  595. *
  596. * @return bool
  597. */
  598. protected function _save_settings()
  599. {
  600. if( ! $this->_settings ) return;
  601. foreach( $this->_settings as $key => $value ) {
  602. $value = maybe_serialize( $value );
  603. $this->_results[] = $this->_save_setting( $key, $value );
  604. }
  605. $this->_save_parent_relationship();
  606. return $this->_results;
  607. }
  608. /**
  609. * Save Parent Relationship
  610. *
  611. * @return $this
  612. */
  613. protected function _save_parent_relationship()
  614. {
  615. // ID, Type, Parent ID, and Parent Type are required for creating a relationship.
  616. if( ! $this->_id || ! $this->_type || ! $this->_parent_id || ! $this->_parent_type ) return $this;
  617. // Check to see if a relationship exists.
  618. $this->_db->get_results(
  619. "
  620. SELECT *
  621. FROM $this->_relationships_table
  622. WHERE `child_id` = $this->_id
  623. AND `child_type` = '$this->_type'
  624. "
  625. );
  626. // If a relationship does not exists, then create one.
  627. if( 0 == $this->_db->num_rows ) {
  628. $this->_db->insert(
  629. $this->_relationships_table,
  630. array(
  631. 'child_id' => $this->_id,
  632. 'child_type' => $this->_type,
  633. 'parent_id' => $this->_parent_id,
  634. 'parent_type' => $this->_parent_type
  635. ),
  636. array(
  637. '%d',
  638. '%s',
  639. '%d',
  640. '%s',
  641. )
  642. );
  643. }
  644. // Return the current object for method chaining.
  645. return $this;
  646. }
  647. /**
  648. * Build Meta Query
  649. *
  650. * @param string $parent_id
  651. * @param array $where
  652. * @return string
  653. */
  654. protected function build_meta_query( $parent_id = '', array $where = array() )
  655. {
  656. $join_statement = array();
  657. $where_statement = array();
  658. if( $where AND is_array( $where ) ) {
  659. $where_conditions = array();
  660. foreach ($where as $key => $value) {
  661. $conditions['key'] = $key;
  662. $conditions['value'] = $value;
  663. $where_conditions[] = $conditions;
  664. }
  665. $count = count($where);
  666. for ($i = 0; $i < $count; $i++) {
  667. $join_statement[] = "INNER JOIN " . $this->_meta_table_name . " as meta$i on meta$i.parent_id = " . $this->_table_name . ".id";
  668. $where_statement[] = "( meta$i.key = '" . $where_conditions[$i]['key'] . "' AND meta$i.value = '" . $where_conditions[$i]['value'] . "' )";
  669. }
  670. }
  671. $join_statement = implode( ' ', $join_statement );
  672. $where_statement = implode( ' AND ', $where_statement );
  673. // TODO: Breaks SQL. Needs more testing.
  674. // if( $where_statement ) $where_statement = "AND " . $where_statement;
  675. if( $parent_id ){
  676. $where_statement = "$this->_table_name.parent_id = $parent_id $where_statement";
  677. }
  678. if( ! empty( $where_statement ) ) {
  679. $where_statement = "WHERE $where_statement";
  680. }
  681. return "SELECT DISTINCT $this->_table_name.id FROM $this->_table_name $join_statement $where_statement";
  682. }
  683. }