1<?php
2/**
3 * Horde specific wrapper for Horde_Share drivers. Adds Horde hook calls etc...
4 *
5 * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
6 *
7 * See the enclosed file COPYING for license information (LGPL). If you did
8 * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
9 *
10 * @author Michael J. Rubinsky <mrubinsk@horde.org>
11 * @category Horde
12 * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL
13 * @package  Core
14 */
15class Horde_Core_Share_Driver
16{
17    /**
18     * The composed Horde_Share driver
19     *
20     * @var Horde_Share_Base
21     */
22    protected $_share;
23
24    /**
25     * Maps the concrete share class to the required storage adapter.
26     *
27     * @var array
28     */
29    protected $_storageMap = array(
30        'Horde_Share_Sql' => 'Horde_Db_Adapter',
31        'Horde_Share_Sqlng' => 'Horde_Db_Adapter',
32        'Horde_Share_Sql_Hierarchical' => 'Horde_Db_Adapter',
33        'Horde_Share_Kolab' => 'Horde_Kolab_Storage');
34
35    /**
36     */
37    public function __construct(Horde_Share_Base $share)
38    {
39        global $injector;
40
41        $this->_share = $share;
42        $this->_share->setStorage($injector->getInstance($this->_storageMap[get_class($this->_share)]));
43        $this->_share->addCallback('add', array($this, 'shareAddCallback'));
44        $this->_share->addCallback('modify', array($this, 'shareModifyCallback'));
45        $this->_share->addCallback('remove', array($this, 'shareRemoveCallback'));
46        $this->_share->addCallback('list', array($this, 'shareListCallback'));
47
48        try {
49            $injector->getInstance('Horde_Core_Hooks')->callHook('share_init', 'horde', array($this, $this->_share->getApp()));
50        } catch (Horde_Exception_HookNotSet $e) {}
51    }
52
53    /**
54     * Delegate method calls to the composed share object.
55     *
56     * @param string $method  The method name
57     * @param array $args     The method arguments
58     *
59     * @return mixed  The result of the method call
60     */
61    public function __call($method, $args)
62    {
63        return call_user_func_array(array($this->_share, $method), $args);
64    }
65
66    /**
67     * Lock an item belonging to a share, or an entire share itself.
68     *
69     * @param Horde_Lock $locks  The lock object.
70     * @param string $uid        The uid of a specific object to lock, if
71     *                           null, entire share is locked.
72     *
73     * @return mixed  A lock ID on sucess, false if:
74     *   - The share is already locked,
75     *   - The item is already locked,
76     *   - A share lock was requested and an item is already locked in the
77     *     share.
78     */
79    public function lock(Horde_Lock $locks, $uid = null)
80    {
81        $shareid = $this->_share->getId();
82
83        // Default parameters.
84        $locktype = Horde_Lock::TYPE_EXCLUSIVE;
85        $timeout = 600;
86        $itemscope = $this->_share->getShareOb()->getApp() . ':' . $shareid;
87
88        if (!empty($uid)) {
89            // Check if the share is locked. Share locks are placed at app scope
90            try {
91                $result = $locks->getLocks($this->_share->getShareOb()->getApp(), $shareid, $locktype);
92            } catch (Horde_Lock_Exception $e) {
93                throw new Horde_Exception_Wrapped($e);
94            }
95            if (!empty($result)) {
96                // Lock found.
97                return false;
98            }
99
100            // Try to place the item lock at app:shareid scope.
101            return $locks->setLock($GLOBALS['registry']->getAuth(),
102                                   $itemscope,
103                                   $uid,
104                                   $timeout,
105                                   $locktype);
106        } else {
107            // Share lock requested. Check for locked items.
108            try {
109                $result = $locks->getLocks($itemscope, null, $locktype);
110            } catch (Horde_Lock_Exception $e) {
111                throw new Horde_Exception_Wrapped($e);
112            }
113            if (!empty($result)) {
114                // Lock found.
115                return false;
116            }
117
118            // Try to place the share lock
119            return $locks->setLock($GLOBALS['registry']->getAuth(),
120                                   $this->_share->getShareOb()->getApp(),
121                                   $shareid,
122                                   $timeout,
123                                   $locktype);
124        }
125    }
126
127    /**
128     * Removes the lock for a lock ID.
129     *
130     * @param Horde_Lock $locks  The lock object
131     * @param string $lockid     The lock ID as generated by a previous call
132     *                           to lock().
133     *
134     * @return boolean
135     */
136    public function unlock(Horde_Lock $locks, $lockid)
137    {
138        return $locks->clearLock($lockid);
139    }
140
141    /**
142     * Checks for existing locks.
143     *
144     * First this checks for share locks and if none exists, checks for item
145     * locks (if item_uid defined).  It will return the first lock found.
146     *
147     * @param Horde_Lock  $locks  The lock object.
148     * @param string $item_uid    A uid of an item from this share.
149     *
150     * @return array   Hash with the found lock information in 'lock' and the
151     *                 lock type ('share' or 'item') in 'type', or an empty
152     *                 array if there are no locks.
153     */
154    public function checkLocks(Horde_Lock $locks, $item_uid = null)
155    {
156        $shareid = $this->_share->getId();
157        $locktype = Horde_Lock::TYPE_EXCLUSIVE;
158
159        // Check for share locks
160        try {
161            $result = $locks->getLocks($this->_share->getShareOb()->getApp(), $shareid, $locktype);
162        } catch (Horde_Lock_Exception $e) {
163            Horde::log($e, 'ERR');
164            throw new Horde_Exception_Wrapped($e);
165        }
166
167        if (empty($result) && !empty($item_uid)) {
168            // Check for item locks
169            $locktargettype = 'item';
170            try {
171                $result = $locks->getLocks($this->_share->getShareOb()->getApp() . ':' . $shareid, $item_uid, $locktype);
172            } catch (Horde_Lock_Exception $e) {
173                Horde::log($e, 'ERR');
174                throw new Horde_Exception($e->getMessage());
175            }
176        } else {
177            $locktargettype = 'share';
178        }
179
180        if (empty($result)) {
181            return array();
182        }
183
184        return array('type' => $locktargettype,
185                     'lock' => reset($result));
186    }
187
188    /**
189     * share_list callback
190     *
191     * @param string $userid  The userid listShares was called with
192     * @param array  $shares  The result of the listShares() call
193     * @param array  $params  The params that listShares() was called with
194     *
195     * @return array  An array of share objects
196     */
197    public function shareListCallback($userid, $shares, $params = array())
198    {
199        try {
200            $params = new Horde_Support_Array($params);
201            return $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
202                ->callHook('share_list', 'horde', array($userid, $params['perm'], $params['attributes'], $shares));
203        } catch (Horde_Exception_HookNotSet $e) {}
204
205        return $shares;
206    }
207
208    /**
209     * Adds the share_add hook before delegating to the share object.
210     *
211     * @param Horde_Share_Object  The share object being added
212     */
213    public function shareAddCallback(Horde_Share_Object $share)
214    {
215        try {
216            $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
217                ->callHook('share_add', 'horde', array($share));
218        } catch (Horde_Exception_HookNotSet $e) {}
219    }
220
221    /**
222     * Calls the share_remove hook before delegating to the share object.
223     *
224     * @see Horde_Share_Base::removeShare()
225     */
226    public function shareRemoveCallback(Horde_Share_Object $share)
227    {
228        try {
229            $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
230                ->callHook('share_remove', 'horde', array($share));
231        } catch (Horde_Exception_HookNotSet $e) {}
232    }
233
234    public function shareModifyCallback(Horde_Share_Object $share)
235    {
236        try {
237            $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
238                ->callHook('share_modify', 'horde', array($share));
239        } catch (Horde_Exception_HookNotSet $e) {}
240    }
241
242}
243