1: <?php
2: /**
3: * Portions Copyright 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
4: * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
5: *
6: * @category Horde
7: * @package Feed
8: */
9:
10: /**
11: * Concrete class for working with Atom entries.
12: *
13: * @category Horde
14: * @package Feed
15: */
16: class Horde_Feed_Entry_Atom extends Horde_Feed_Entry_Base
17: {
18: /**
19: * The XML string for an "empty" Atom entry.
20: *
21: * @var string
22: */
23: protected $_emptyXml = '<atom:entry/>';
24:
25: /**
26: * Name of the XML element for Atom entries. Subclasses can
27: * override this to something other than "entry" if necessary.
28: *
29: * @var string
30: */
31: protected $_entryElementName = 'entry';
32:
33: /**
34: * Delete an atom entry.
35: *
36: * Delete tries to delete this entry from its feed. If the entry
37: * does not contain a link rel="edit", we throw an error (either
38: * the entry does not yet exist or this is not an editable
39: * feed). If we have a link rel="edit", we do the empty-body
40: * HTTP DELETE to that URI and check for a response of 2xx.
41: * Usually the response would be 204 No Content, but the Atom
42: * Publishing Protocol permits it to be 200 OK.
43: *
44: * @throws Horde_Feed_Exception If an error occurs, an Horde_Feed_Exception will
45: * be thrown.
46: */
47: public function delete()
48: {
49: // Look for link rel="edit" in the entry object.
50: $deleteUri = $this->link('edit');
51: if (!$deleteUri) {
52: throw new Horde_Feed_Exception('Cannot delete entry; no link rel="edit" is present.');
53: }
54:
55: // DELETE
56: do {
57: $response = $this->_httpClient->delete($deleteUri);
58: switch ((int)$response->code / 100) {
59: // Success
60: case 2:
61: return true;
62:
63: // Redirect
64: case 3:
65: $deleteUri = $response->getHeader('Location');
66: continue;
67:
68: // Error
69: default:
70: throw new Horde_Feed_Exception('Expected response code 2xx, got ' . $response->code);
71: }
72: } while (true);
73: }
74:
75: /**
76: * Save a new or updated Atom entry.
77: *
78: * Save is used to either create new entries or to save changes to existing
79: * ones. If we have a link rel="edit", we are changing an existing entry. In
80: * this case we re-serialize the entry and PUT it to the edit URI, checking
81: * for a 200 OK result.
82: *
83: * For posting new entries, you must specify the $postUri parameter to
84: * save() to tell the object where to post itself. We use $postUri and POST
85: * the serialized entry there, checking for a 201 Created response. If the
86: * insert is successful, we then parse the response from the POST to get any
87: * values that the server has generated: an id, an updated time, and its new
88: * link rel="edit".
89: *
90: * @param string $postUri Location to POST for creating new entries.
91: * @param array $headers Additional headers to transmit while posting.
92: *
93: * @throws Horde_Feed_Exception If an error occurs, a Horde_Feed_Exception
94: * will be thrown.
95: */
96: public function save($postUri = null, $headers = array())
97: {
98: $headers = array_merge(
99: array('Content-Type' => 'application/atom+xml'),
100: $headers
101: );
102:
103: if ($this->id()) {
104: // If id is set, look for link rel="edit" in the
105: // entry object and PUT.
106: $editUri = $this->link('edit');
107: if (!$editUri) {
108: throw new Horde_Feed_Exception('Cannot edit entry; no link rel="edit" is present.');
109: }
110:
111: $response = $this->_httpClient->put($editUri, $this->saveXml(), $headers);
112: if ($response->code !== 200) {
113: throw new Horde_Feed_Exception('Expected response code 200, got ' . $response->code);
114: }
115: } else {
116: if ($postUri === null) {
117: throw new Horde_Feed_Exception('PostURI must be specified to save new entries.');
118: }
119: $response = $this->_httpClient->post($postUri, $this->saveXml(), $headers);
120: if ($response->code !== 201) {
121: throw new Horde_Feed_Exception('Expected response code 201, got ' . $response->code);
122: }
123: }
124:
125: // Update internal properties using the response body.
126: $body = $response->getBody();
127: $newEntry = new DOMDocument;
128: $parsed = @$newEntry->loadXML($body);
129: if (!$parsed) {
130: throw new Horde_Feed_Exception('DOMDocument cannot parse XML: ', error_get_last());
131: }
132:
133: $newEntry = $newEntry->getElementsByTagName($this->_entryElementName)->item(0);
134: if (!$newEntry) {
135: throw new Horde_Feed_Exception('No root <feed> element found in server response:' . "\n\n" . $body);
136: }
137:
138: if ($this->_element->parentNode) {
139: $oldElement = $this->_element;
140: $this->_element = $oldElement->ownerDocument->importNode($newEntry, true);
141: $oldElement->parentNode->replaceChild($this->_element, $oldElement);
142: } else {
143: $this->_element = $newEntry;
144: }
145:
146: $this->_expireCachedChildren();
147: }
148:
149: /**
150: * Easy access to <link> tags keyed by "rel" attributes.
151: * @TODO rationalize this with other __get/__call access
152: *
153: * If $elt->link() is called with no arguments, we will attempt to
154: * return the value of the <link> tag(s) like all other
155: * method-syntax attribute access. If an argument is passed to
156: * link(), however, then we will return the "href" value of the
157: * first <link> tag that has a "rel" attribute matching $rel:
158: *
159: * $elt->link(): returns the value of the link tag.
160: * $elt->link('self'): returns the href from the first <link rel="self"> in the entry.
161: *
162: * @param string $rel The "rel" attribute to look for.
163: * @return mixed
164: */
165: public function link($rel = null)
166: {
167: if ($rel === null) {
168: return parent::__call('link', null);
169: }
170:
171: // index link tags by their "rel" attribute.
172: $links = parent::__get('link');
173: if (!is_array($links)) {
174: if ($links instanceof Horde_Xml_Element) {
175: $links = array($links);
176: } else {
177: return $links;
178: }
179: }
180:
181: foreach ($links as $link) {
182: if (empty($link['rel'])) {
183: continue;
184: }
185: if ($rel == $link['rel']) {
186: return $link['href'];
187: }
188: }
189:
190: return null;
191: }
192: }
193: