1<?php
2/**
3 * @package Habari
4 *
5 */
6
7/**
8 * Habari InfoRecords Class
9 *
10 * Base class for managing metadata about various Habari objects
11 *
12 */
13abstract class InfoRecords implements URLProperties
14{
15	// the info array
16	protected $__inforecord_array = array();
17	// table which contains the info records
18	protected $_table_name;
19	// name of the primary key in the info record table
20	protected $_key_name;
21	// value of the primary key - the master record
22	protected $_key_value;
23	// set to true only when the inforecords have been loaded
24	protected $_loaded = false;
25
26	protected $url_args;
27
28	/**
29	 * Takes three parameters. The table of the options table, the name of the master key  and the record_id for which options are managed.
30	 * posts take a slug as a record_id, comments take a comment_id and users take a user_id (user_name)
31	 *
32	 * <b>IMPORTANT:</b> if <tt>$primary_key_value</tt> is not set in the constructor, set_key MUST be called before inforecords can be set. Or bad things
33	 * (think swarms of locusts o'er the land) will happen
34	 *
35	 * @param string $table_name name of the table to insert info (use the DB::o()->table_name syntax)
36	 * @param string $key_name name of the primary key (for example "post_id")
37	 * @param mixed $key_value (optional) the master record key value (for example, info for post_id = 1 managed by setting this param to 1). Use
38	 *		set_key method if not set here.
39	 **/
40	public function __construct( $table_name, $key_name, $key_value = null )
41	{
42		$this->_table_name = $table_name;
43		$this->_key_name = $key_name;
44		$this->_key_value = $key_value;
45		$this->_loaded = false;
46	}
47
48	/**
49	 * Test if the master record value has been set (and thus, safe to set info records).
50	 *
51	 * @return boolean true if master record value has been set already, false otherwise
52	 **/
53	public function is_key_set()
54	{
55		return isset( $this->_key_value );
56	}
57
58	/**
59	 * function set_key
60	 * For use in cases where the master record key is not known at the time of object instantiation (ie: a new post)
61	 *
62	 * @param mixed $metadata_key the id of the master record (could be int or string, most likely int)
63	 *
64	 **/
65	public function set_key( $metadata_key )
66	{
67		$this->_key_value = $metadata_key;
68	}
69
70	/**
71	 * Populate the internal hashmap with the values from the DB.
72	 */
73	protected function _load()
74	{
75		if ( $this->_loaded == true ) {
76			return;
77		}
78		if ( empty($this->_key_value) ) {
79			$this->_loaded == true;
80			return;
81		}
82
83		// This InfoRecord is read-only?
84		if ( empty($this->_table_name) ) {
85			$this->_loaded == true;
86			return;
87		}
88
89		$result = DB::get_results( '
90			SELECT name, value, type
91			FROM ' . $this->_table_name . '
92			WHERE ' . $this->_key_name . ' = ?',
93			array( $this->_key_value )
94		);
95
96		foreach ( $result as $result_element ) {
97			// XXX is this logic right?
98			if ( $result_element->type == 1 ) {
99				$this->__inforecord_array[$result_element->name] = array( 'value' => unserialize( $result_element->value ) );
100			}
101			else {
102				$this->__inforecord_array[$result_element->name] = array( 'value' => $result_element->value );
103			}
104		}
105
106		$this->_loaded = true;
107	}
108
109	/**
110	 * Fetch info record value.
111	 * @param string $name Name of the key to get
112	 * @return mixed Stored value for specified key
113	 **/
114	public function __get ( $name )
115	{
116		$this->_load();
117		if ( ! isset( $this->__inforecord_array[$name] ) ) {
118			return false;
119		}
120		return $this->__inforecord_array[$name]['value'];
121	}
122
123	/**
124	 * Update the info record.
125	 * The value will not be stored in the database until calling $this->commit();
126	 *
127	 * @param string $name Name of the key to set
128	 * @param mixed $value Value to set
129	 **/
130	public function __set( $name, $value )
131	{
132		$this->_load();
133		$this->__inforecord_array[$name] = array('changed'=>true, 'value'=>$value);
134	}
135
136	/**
137	 * Test for the existence of specified info value
138	 *
139	 * @param string $name Name of the option to set
140	 * @return boolean true if the info option exists, false in all other cases
141	 **/
142	public function __isset ( $name )
143	{
144		$this->_load();
145		return isset( $this->__inforecord_array[$name] );
146	}
147
148	/**
149	 * Remove an info option; immediately unsets from the storage AND removes from database. Use with caution.
150	 *
151	 * @param string $name Name of the option to unset
152	 * @return boolean true if the option is successfully unset, false otherwise
153	 **/
154	public function __unset( $name )
155	{
156		$this->_load();
157		if ( isset( $this->__inforecord_array[$name] ) ) {
158			DB::delete( $this->_table_name, array ( $this->_key_name => $this->_key_value, "name" => $name ) );
159			unset( $this->__inforecord_array[$name] );
160			return true;
161		}
162		return false;
163	}
164
165	/**
166	 * Returns a set of properties used by URL::get to create URLs
167	 * @return array Properties of this post used to build a URL
168	 **/
169	public function get_url_args()
170	{
171		if ( !$this->url_args ) {
172			$this->_load();
173			$this->url_args = array_map( create_function( '$a', 'return $a["value"];' ), $this->__inforecord_array );
174		}
175		return $this->url_args;
176	}
177
178	/**
179	 * Remove all info options. Primarily used when deleting the parent object.
180	 * I.E. when deleting a user, the delete method would call this.
181	 *
182	 * @return boolean true if the options were successfully unset, false otherwise
183	 **/
184	public function delete_all()
185	{
186		// This InfoRecord is read-only?
187		if ( empty($this->_table_name) ) {
188			return;
189		}
190		$result = DB::query( '
191			DELETE FROM ' . $this->_table_name . '
192			WHERE ' . $this->_key_name . ' = ?',
193			array( $this->_key_value )
194		);
195		if ( Error::is_error( $result ) ) {
196			$result->out();
197			return false;
198		}
199		$this->__inforecord_array = array();
200		return true;
201	}
202
203	/**
204	 * Commit all of the changed info options to the database.
205	 * If this function is not called, then the options will not be written.
206	 *
207	 * @param mixed $metadata_key (optional) Key to use when writing info data.
208	 */
209	public function commit( $metadata_key = null )
210	{
211		if ( isset( $metadata_key ) ) {
212			$this->_key_value = $metadata_key;
213		}
214		// If the info is not already loaded, and the key value is empty,
215		// then we don't have enough info to do the commit
216		if ( !$this->_loaded && empty($this->_key_value) ) {
217			return true;
218		}
219
220		foreach ( (array)$this->__inforecord_array as $name=>$record ) {
221			if ( isset( $record['changed'] ) && $record['changed'] ) {
222				$value = $record['value'];
223				if ( is_array( $value ) || is_object( $value ) ) {
224					$result = DB::update(
225						$this->_table_name,
226						array(
227							$this->_key_name=>$this->_key_value,
228							'name'=>$name,
229							'value'=>serialize( $value ),
230							'type'=>1
231						),
232						array( 'name'=>$name, $this->_key_name=>$this->_key_value )
233					);
234				}
235				else {
236					$result = DB::update(
237						$this->_table_name,
238						array(
239							$this->_key_name=>$this->_key_value,
240							'name'=>$name,
241							'value'=>$value,
242							'type'=>0
243						),
244						array('name'=>$name, $this->_key_name=> $this->_key_value)
245					);
246				}
247
248				if ( Error::is_error( $result ) ) {
249					$result->out();
250				}
251				$this->__inforecord_array[$name] = array('value'=>$value);
252			}
253		}
254	}
255
256	public function count()
257	{
258		$this->_load();
259		return count( $this->__inforecord_array );
260
261	}
262
263}
264
265?>
266