1: <?php
2: /**
3: * Handles a directory in the contents list.
4: *
5: * PHP version 5
6: *
7: * @category Horde
8: * @package Pear
9: * @author Gunnar Wrobel <wrobel@pardus.de>
10: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
11: * @link http://pear.horde.org/index.php?package=Pear
12: */
13:
14: /**
15: * Handles a directory in the contents list.
16: *
17: * Copyright 2011-2012 Horde LLC (http://www.horde.org/)
18: *
19: * See the enclosed file COPYING for license information (LGPL). If you
20: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
21: *
22: * @category Horde
23: * @package Pear
24: * @author Gunnar Wrobel <wrobel@pardus.de>
25: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
26: * @link http://pear.horde.org/index.php?package=Pear
27: */
28: class Horde_Pear_Package_Xml_Directory
29: {
30: /**
31: * The directory node.
32: *
33: * @var Horde_Pear_Package_Xml_Element_Directory
34: */
35: private $_element;
36:
37: /**
38: * The parent directory.
39: *
40: * @var Horde_Pear_Package_Xml_Directory
41: */
42: private $_parent;
43:
44: /**
45: * The list of subdirectories.
46: *
47: * @var array
48: */
49: private $_subdirectories = array();
50:
51: /**
52: * The list of files in this directory.
53: *
54: * @var array
55: */
56: private $_files;
57:
58: /**
59: * Constructor.
60: *
61: * @param Horde_Pear_Package_Xml_Element_Directory $dir The directory element.
62: * @param mixed $parent The parent directory
63: * or the XML document.
64: */
65: public function __construct(Horde_Pear_Package_Xml_Element_Directory $dir,
66: $parent)
67: {
68: $this->_element = $dir;
69: $this->_parent = $parent;
70: $subdirectories = $this->_element->getSubdirectories();
71: foreach ($subdirectories as $name => $element) {
72: $this->_subdirectories[$name] = $this->_create($element, $this);
73: }
74: $this->_files = $this->_element->getFiles();
75: }
76:
77: /**
78: * Return the directory node.
79: *
80: * @return DOMNode The directory node.
81: */
82: public function getDirectory()
83: {
84: return $this->_element;
85: }
86:
87: /**
88: * Return the list of files in this hierarchy.
89: *
90: * @return array The file list.
91: */
92: public function getFiles()
93: {
94: $result = array();
95: foreach ($this->_subdirectories as $directory) {
96: $result = array_merge(
97: $result,
98: array_map(
99: array($this, '_prependDirectory'),
100: $directory->getFiles()
101: )
102: );
103: }
104: $result = array_merge(
105: $result,
106: array_map(
107: array($this, '_prependDirectory'),
108: array_keys($this->_files)
109: )
110: );
111: return $result;
112: }
113:
114: /**
115: * Create a new directory handler.
116: *
117: * @param Horde_Pear_Package_Xml_Element_Directory $element The represented element.
118: * @param Horde_Pear_Package_Xml_Directory $parent The parent directory.
119: *
120: * @return Horde_Pear_Package_Xml_Directory
121: */
122: private function _getRoot()
123: {
124: if ($this->_parent instanceOf Horde_Pear_Package_Xml_Directory) {
125: return $this->_parent->_getRoot();
126: } else {
127: return $this->_parent;
128: }
129: }
130:
131: /**
132: * Create a new directory handler.
133: *
134: * @param Horde_Pear_Package_Xml_Element_Directory $element The represented element.
135: * @param Horde_Pear_Package_Xml_Directory $parent The parent directory.
136: *
137: * @return Horde_Pear_Package_Xml_Directory
138: */
139: private function _create(Horde_Pear_Package_Xml_Element_Directory $element,
140: Horde_Pear_Package_Xml_Directory $parent)
141: {
142: return $this->_getRoot()->createDirectory($element, $parent);
143: }
144:
145: /**
146: * Prepend the directory name of this directory to the path name.
147: *
148: * @param string $path The input path name.
149: *
150: * @return The completed path.
151: */
152: private function _prependDirectory($path)
153: {
154: return strtr(
155: $this->_element->getName() . '/' . $path, array('//' => '/')
156: );
157: }
158:
159: /**
160: * Add a file to the list.
161: *
162: * @param string $file The file name.
163: * @param array $params Additional file parameters.
164: *
165: * @return NULL
166: */
167: public function addFile($file, $params)
168: {
169: $this->getParent(explode('/', dirname($file)))
170: ->_addFile($file, $params);
171: }
172:
173: /**
174: * Add a file to the list.
175: *
176: * @param string $file The file name.
177: * @param array $params Additional file parameters.
178: *
179: * @return NULL
180: */
181: private function _addFile($file, $params)
182: {
183: $this->_files[basename($file)] = $this->_element->insertFile(
184: basename($file),
185: $params['role'],
186: $this->_getFileInsertionPoint(basename($file))
187: );
188: }
189:
190: /**
191: * Delete a file from the list.
192: *
193: * @param string $file The file name.
194: *
195: * @return NULL
196: */
197: public function deleteFile($file)
198: {
199: $this->getParent(explode('/', dirname($file)))->_deleteFile($file);
200: }
201:
202: /**
203: * Delete a file from the list.
204: *
205: * @param string $file The file name.
206: *
207: * @return NULL
208: */
209: private function _deleteFile($file)
210: {
211: $this->_files[basename($file)]->delete();
212: unset($this->_files[basename($file)]);
213: $this->_prune();
214: }
215:
216: /**
217: * Delete a subdirectory from the list.
218: *
219: * @param string $dir The directory name.
220: *
221: * @return NULL
222: */
223: private function _deleteSubdirectory($dir)
224: {
225: unset($this->_subdirectories[$dir]);
226: $this->_prune();
227: }
228:
229: /**
230: * Prune this directory if it is empty.
231: *
232: * @return NULL
233: */
234: private function _prune()
235: {
236: if (empty($this->_files) && empty($this->_subdirectories)) {
237: $this->_element->delete();
238: if ($this->_parent instanceOf Horde_Pear_Package_Xml_Directory) {
239: $this->_parent->_deleteSubdirectory($this->_element->getName());
240: }
241: }
242: }
243:
244: /**
245: * Ensure the provided path hierarchy.
246: *
247: * @param array $tree The path elements that are required.
248: *
249: * @return DOMNode The parent directory for the file.
250: */
251: public function getParent($tree)
252: {
253: $next = array_shift($tree);
254: while ($next === '') {
255: $next = array_shift($tree);
256: }
257: if (empty($tree) && !strlen($next)) {
258: return $this;
259: }
260: if (!isset($this->_subdirectories[$next])) {
261: $this->_subdirectories[$next] = $this->_create(
262: $this->_element->insertSubDirectory(
263: $next,
264: $this->_getDirectoryInsertionPoint($next)
265: ),
266: $this
267: );
268: }
269: return $this->_subdirectories[$next]->getParent($tree);
270: }
271:
272: /**
273: * Identify the insertion point for a new directory.
274: *
275: * @param string $new The key for the new element.
276: *
277: * @return mixed The insertion point.
278: */
279: private function _getDirectoryInsertionPoint($new)
280: {
281: $keys = array_keys($this->_subdirectories);
282: array_push($keys, $new);
283: usort($keys, array($this, '_fileOrder'));
284: $pos = array_search($new, $keys);
285: if ($pos < count($this->_subdirectories)) {
286: return $this->_subdirectories[$keys[$pos + 1]]->getDirectory()->getDirectoryNode();
287: } else {
288: if (empty($this->_files)) {
289: return null;
290: } else {
291: $keys = array_keys($this->_files);
292: usort($keys, array($this, '_fileOrder'));
293: return $this->_files[$keys[0]]->getFileNode();
294: }
295: }
296: }
297:
298: /**
299: * Sort order for files in the content list.
300: *
301: * @param string $a First path.
302: * @param string $b Second path.
303: *
304: * @return int Sort comparison result.
305: */
306: private function _fileOrder($a, $b)
307: {
308: return strnatcasecmp($a, $b);
309: }
310:
311: /**
312: * Identify the insertion point for a new file.
313: *
314: * @param string $new The key for the new element.
315: *
316: * @return mixed The insertion point.
317: */
318: private function _getFileInsertionPoint($new)
319: {
320: $keys = array_keys($this->_files);
321: array_push($keys, $new);
322: usort($keys, array($this, '_fileOrder'));
323: $pos = array_search($new, $keys);
324: if ($pos < count($this->_files)) {
325: return $this->_files[$keys[$pos + 1]]->getFileNode();
326: } else {
327: return null;
328: }
329: }
330: }