1: <?php
2: /**
3: * Beatnik_Driver:: defines an API implementing astorage backends for Beatnik.
4: *
5: * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
6: *
7: * See the enclosed file COPYING for license information (GPL). If you
8: * did not receive this file, see http://www.horde.org/licenses/gpl.
9: *
10: * @author Ben Klang <ben@alkaloid.net>
11: * @package Beatnik
12: */
13: class Beatnik_Driver {
14:
15: /**
16: * Hash containing connection parameters.
17: *
18: * @var array $_params
19: */
20: var $_params = array();
21:
22: function Beatnik_Driver($params = array())
23: {
24: $this->_params = $params;
25: }
26:
27: /**
28: * Get any record types available specifically in this driver.
29: *
30: * @return array Records available only to this driver
31: */
32: function getRecDriverTypes()
33: {
34: return array();
35: }
36:
37:
38: /**
39: * Get any fields available specifically in this driver by record type.
40: *
41: * @param string $type Record type for which fields should be returned
42: *
43: * @return array Fields specific to this driver
44: */
45: function getRecDriverFields($type) {
46:
47: return array();
48: }
49:
50: /**
51: * Gets domains from driver for which the user has the specified
52: * permission.
53: *
54: * @param int $perms Permissions filter for domain result set
55: *
56: * @return array Possibly empty array of domain information
57: */
58: function getDomains($perms = Horde_Perms::SHOW)
59: {
60: try {
61: $domains = $this->_getDomains();
62: } catch (Exception $e) {
63: $GLOBALS['notification']->push($e->getMessage(), 'horde.warning');
64: return array();
65: }
66:
67: if (!$GLOBALS['registry']->isAdmin() &&
68: !$GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission('beatnik:domains', $GLOBALS['registry']->getAuth(), $perms)) {
69: foreach ($domains as $id => $domain) {
70: if (!$GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission('beatnik:domains:' . $domain['zonename'], $GLOBALS['registry']->getAuth(), $perms)) {
71: unset($domains[$id]);
72: }
73: }
74: }
75:
76: if (empty($domains)) {
77: $GLOBALS['notification']->push(_("You are not permitted to view any domains."), 'horde.warning');
78: return array();
79: }
80:
81: // Sort the resulting list by domain name
82: // TODO: Allow sorting by other columns
83: require_once 'Horde/Array.php';
84: Horde_Array::arraySort($domains, 'zonename');
85:
86: return $domains;
87: }
88:
89: /**
90: * Return SOA for a single domain
91: *
92: * @param string $domain Domain for which to return SOA information
93: *
94: * @return mixed Array of SOA information for domain
95: */
96: function getDomain($domainname)
97: {
98: $domains = $this->getDomains(Horde_Perms::SHOW | Horde_Perms::READ);
99:
100: foreach ($domains as $domain) {
101: if ($domain['zonename'] == $domainname) {
102: return $domain;
103: }
104: }
105: throw new Beatnik_Exception(sprintf(_("Unable to read requested domain %s"), $domainname));
106: }
107:
108: /**
109: * Gets a specific record from the backend. This method may be overridden
110: * in specific backend drivers if there is a performance or other benefit
111: * for doing so.
112: *
113: * @return array Array of type and record information
114: */
115: function getRecord($id)
116: {
117: if ($id === null) {
118: return false;
119: }
120:
121: $zonedata = $this->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
122: // Search for the requested record id
123: foreach ($zonedata as $type => $records) {
124: foreach ($records as $record) {
125: if ($record['id'] == $id) {
126: // Found the record we're looking for
127: break;
128: }
129: $type = false;
130: $record = false;
131: }
132: if ($record) {
133: // Record found in nested loop. Break out of this one too.
134: break;
135: }
136: }
137:
138: if (!$record) {
139: // We may be editing the SOA. See if it matches
140: $record = $this->getDomain($_SESSION['beatnik']['curdomain']['zonename']);
141: if ($record['id'] == $id) {
142: $type = 'soa';
143: } else {
144: $GLOBALS['notification']->push(_("Unable to locate requested record."), 'horde.error');
145: $type = false;
146: $record = false;
147: }
148: }
149:
150: return array($type, $record);
151: }
152:
153: /**
154: * Try to determinate if the autogenerated record
155: * already exits. This method may be overridden in the backend driver
156: * for performance or other reasons.
157: *
158: * @param array $record record to check
159: *
160: * @return boolean if records exits or or not
161: */
162: function recordExists($record, $rectype)
163: {
164: try {
165: $zonedata = $this->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
166: } catch (Exception $e) {
167: $notification->push($e, 'horde.error');
168: Horde::url('listzones.php')->redirect;
169: }
170:
171: if (isset($zonedata[$rectype])) {
172: foreach ($zonedata[$rectype] as $row) {
173: // Prune empty values from $row to aid in comparison
174: foreach ($row as $key => $value) {
175: if (empty($value)) {
176: unset($row[$key]);
177: }
178: }
179:
180: $same_values = array_intersect_assoc($row, $record);
181: if (count($same_values) == count($record)) {
182: return true;
183: }
184: }
185: }
186:
187: return false;
188: }
189:
190: /**
191: * Saves a record fo the configured driver and checks/sets needCommit()
192: * Also first checks to ensure permission to save record is available.
193: *
194: * @param array $info Data to be passed to backend driver for storage
195: *
196: * @return mixed True on success, PEAR::Error on error
197: */
198: function saveRecord(&$info)
199: {
200: // Check to see if this is a new domain
201: if ($info['rectype'] == 'soa' && $info['zonename'] != $_SESSION['beatnik']['curdomain']['zonename']) {
202: // Make sure the user has permissions to add domains
203: if (!Beatnik::hasPermission('beatnik:domains', Horde_Perms::EDIT)) {
204: throw new Beatnik_Exception(_('You do not have permission to create new domains.'));
205: }
206:
207: // Create a dummy old domain for comparison purposes
208: $oldsoa['serial'] = 0;
209:
210: } else {
211: $oldsoa =& $_SESSION['beatnik']['curdomain'];
212:
213: // Check for permissions to edit the record in question
214: if ($info['rectype'] == 'soa') {
215: $node = 'beatnik:domains:' . $info['zonename'];
216: if (!Beatnik::hasPermission($node, Horde_Perms::EDIT, 1)) {
217: throw new Beatnik_Exception(_('You do not have permssion to edit the SOA of this zone.'));
218: }
219: } else {
220: $node = 'beatnik:domains:' . $_SESSION['beatnik']['curdomain']['zonename'];
221: if (!Beatnik::hasPermission($node, Horde_Perms::EDIT, 2)) {
222: throw new Beatnik_Exception(_('You do not have permssion to edit this record.'));
223: }
224: }
225: }
226:
227: // Save the changes to the backend
228: // FIXME: Modify saveRecord() to return the new (possibly changed) ID of the
229: // record and then use that ID to update permissions
230: $return = $this->_saveRecord($info);
231: $oldsoa =& $_SESSION['beatnik']['curdomain'];
232: if ($info['rectype'] == 'soa' &&
233: ($oldsoa['serial'] < $info['serial'])) {
234: // Clear the commit flag (if set)
235: Beatnik::needCommit($oldsoa['zonename'], false);
236: } else {
237: Beatnik::needCommit($oldsoa['zonename'], true);
238: }
239:
240: // Check to see if an SOA was just added or updated.
241: // If so, make it the current domain.
242: if ($info['rectype'] == 'soa') {
243: $_SESSION['beatnik']['curdomain'] = $this->getDomain($info['zonename']);
244: }
245:
246: return true;
247: }
248:
249: /**
250: * Delete record from backend
251: *
252: * @access private
253: *
254: * @param array $info Reference to array of record information for deletion
255: *
256: * @return boolean true on success
257: */
258: function deleteRecord(&$info)
259: {
260: $return = $this->_deleteRecord($info);
261: $oldsoa =& $_SESSION['beatnik']['curdomain'];
262:
263: // No need to commit if the whole zone is gone
264: if ($info['rectype'] != 'soa') {
265: Beatnik::needCommit($oldsoa['zonename'], true);
266: }
267: }
268:
269: /**
270: * Attempts to return a concrete Beatnik_Driver instance based on
271: * $driver.
272: *
273: * @param string $driver The type of the concrete Beatnik_Driver subclass
274: * to return. The class name is based on the storage
275: * driver ($driver). The code is dynamically
276: * included.
277: *
278: * @param array $params (optional) A hash containing any additional
279: * configuration or connection parameters a
280: * subclass might need.
281: *
282: * @return mixed The newly created concrete Beatnik_Driver instance, or
283: * false on an error.
284: */
285: function factory($driver = null, $params = null)
286: {
287: if ($driver === null) {
288: $driver = $GLOBALS['conf']['storage']['driver'];
289: }
290:
291: $driver = basename($driver);
292: if (empty($driver) || ($driver == 'none')) {
293: return new Horde_Lock();
294: }
295:
296: if (is_null($params)) {
297: // Since we have more than one backend that uses SQL make sure
298: // all of them have a chance to inherit the site-wide config.
299: $sqldrivers = array('sql', 'pdnsgsql');
300: if (in_array($driver, $sqldrivers)) {
301: $params = Horde::getDriverConfig('storage', 'sql');
302: } else {
303: $params = Horde::getDriverConfig('storage', $driver);
304: }
305: }
306:
307: require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
308: $class = 'Beatnik_Driver_' . $driver;
309: if (class_exists($class)) {
310: return new $class($params);
311: } else {
312: throw new Beatnik_Exception(_('Driver not found.'));
313: }
314: }
315:
316: }
317: