1<?php
2class EtherpadLiteClient {
3
4  const API_VERSION             = 1;
5
6  const CODE_OK                 = 0;
7  const CODE_INVALID_PARAMETERS = 1;
8  const CODE_INTERNAL_ERROR     = 2;
9  const CODE_INVALID_FUNCTION   = 3;
10  const CODE_INVALID_API_KEY    = 4;
11
12  protected $apiKey = "";
13  protected $baseUrl = "http://localhost:9001/api";
14
15  public function __construct($apiKey, $baseUrl = null){
16    $this->apiKey  = $apiKey;
17    if (isset($baseUrl)){
18      $this->baseUrl = $baseUrl;
19    }
20    if (!filter_var($this->baseUrl, FILTER_VALIDATE_URL)){
21      throw new InvalidArgumentException("[{$this->baseUrl}] is not a valid URL");
22    }
23  }
24
25  protected function get($function, array $arguments = array()){
26    return $this->call($function, $arguments, 'GET');
27  }
28
29  protected function post($function, array $arguments = array()){
30    return $this->call($function, $arguments, 'POST');
31  }
32
33  protected function call($function, array $arguments = array(), $method = 'GET'){
34    $arguments['apikey'] = $this->apiKey;
35    $arguments = http_build_query($arguments, '', '&');
36    $url = $this->baseUrl."/".self::API_VERSION."/".$function;
37    if ($method !== 'POST'){
38      $url .=  "?".$arguments;
39    }
40    // use curl of it's available
41    if (function_exists('curl_init')){
42      $c = curl_init($url);
43      curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
44      curl_setopt($c, CURLOPT_TIMEOUT, 20);
45      if ($method === 'POST'){
46        curl_setopt($c, CURLOPT_POST, true);
47        curl_setopt($c, CURLOPT_POSTFIELDS, $arguments);
48      }
49      $result = curl_exec($c);
50      curl_close($c);
51    // fallback to plain php
52    } else {
53      $params = array('http' => array('method' => $method, 'ignore_errors' => true, 'header' => 'Content-Type:application/x-www-form-urlencoded'));
54      if ($method === 'POST'){
55        $params['http']['content'] = $arguments;
56      }
57      $context = stream_context_create($params);
58      $fp = fopen($url, 'rb', false, $context);
59      $result = $fp ? stream_get_contents($fp) : null;
60    }
61
62    if(!$result){
63      throw new UnexpectedValueException("Empty or No Response from the server");
64    }
65
66    $result = json_decode($result);
67    if ($result === null){
68      throw new UnexpectedValueException("JSON response could not be decoded");
69    }
70    return $this->handleResult($result);
71  }
72
73  protected function handleResult($result){
74    if (!isset($result->code)){
75      throw new RuntimeException("API response has no code");
76    }
77    if (!isset($result->message)){
78      throw new RuntimeException("API response has no message");
79    }
80    if (!isset($result->data)){
81      $result->data = null;
82    }
83
84    switch ($result->code){
85      case self::CODE_OK:
86        return $result->data;
87      case self::CODE_INVALID_PARAMETERS:
88      case self::CODE_INVALID_API_KEY:
89        throw new InvalidArgumentException($result->message);
90      case self::CODE_INTERNAL_ERROR:
91        throw new RuntimeException($result->message);
92      case self::CODE_INVALID_FUNCTION:
93        throw new BadFunctionCallException($result->message);
94      default:
95        throw new RuntimeException("An unexpected error occurred whilst handling the response");
96    }
97  }
98
99  // GROUPS
100  // Pads can belong to a group. There will always be public pads that doesnt belong to a group (or we give this group the id 0)
101
102  // creates a new group
103  public function createGroup(){
104    return $this->post("createGroup");
105  }
106
107  // this functions helps you to map your application group ids to etherpad lite group ids
108  public function createGroupIfNotExistsFor($groupMapper){
109    return $this->post("createGroupIfNotExistsFor", array(
110      "groupMapper" => $groupMapper
111    ));
112  }
113
114  // deletes a group
115  public function deleteGroup($groupID){
116    return $this->post("deleteGroup", array(
117      "groupID" => $groupID
118    ));
119  }
120
121  // returns all pads of this group
122  public function listPads($groupID){
123    return $this->get("listPads", array(
124      "groupID" => $groupID
125    ));
126  }
127
128  // creates a new pad in this group
129  public function createGroupPad($groupID, $padName, $text){
130    return $this->post("createGroupPad", array(
131      "groupID" => $groupID,
132      "padName" => $padName,
133      "text"    => $text
134    ));
135  }
136
137  // AUTHORS
138  // Theses authors are bind to the attributes the users choose (color and name).
139
140  // creates a new author
141  public function createAuthor($name){
142    return $this->post("createAuthor", array(
143      "name" => $name
144    ));
145  }
146
147  // this functions helps you to map your application author ids to etherpad lite author ids
148  public function createAuthorIfNotExistsFor($authorMapper, $name){
149    return $this->post("createAuthorIfNotExistsFor", array(
150      "authorMapper" => $authorMapper,
151      "name"         => $name
152    ));
153  }
154
155  // SESSIONS
156  // Sessions can be created between a group and a author. This allows
157  // an author to access more than one group. The sessionID will be set as
158  // a cookie to the client and is valid until a certian date.
159
160  // creates a new session
161  public function createSession($groupID, $authorID, $validUntil){
162    return $this->post("createSession", array(
163      "groupID"    => $groupID,
164      "authorID"   => $authorID,
165      "validUntil" => $validUntil
166    ));
167  }
168
169  // deletes a session
170  public function deleteSession($sessionID){
171    return $this->post("deleteSession", array(
172      "sessionID" => $sessionID
173    ));
174  }
175
176  // returns informations about a session
177  public function getSessionInfo($sessionID){
178    return $this->get("getSessionInfo", array(
179      "sessionID" => $sessionID
180    ));
181  }
182
183  // returns all sessions of a group
184  public function listSessionsOfGroup($groupID){
185    return $this->get("listSessionsOfGroup", array(
186      "groupID" => $groupID
187    ));
188  }
189
190  // returns all sessions of an author
191  public function listSessionsOfAuthor($authorID){
192    return $this->get("listSessionsOfAuthor", array(
193      "authorID" => $authorID
194    ));
195  }
196
197  // PAD CONTENT
198  // Pad content can be updated and retrieved through the API
199
200  // returns the text of a pad
201  public function getText($padID, $rev=null){
202    $params = array("padID" => $padID);
203    if (isset($rev)){
204      $params["rev"] = $rev;
205    }
206    return $this->get("getText", $params);
207  }
208
209  // returns the text of a pad as html
210  public function getHTML($padID, $rev=null){
211    $params = array("padID" => $padID);
212    if (isset($rev)){
213      $params["rev"] = $rev;
214    }
215    return $this->get("getHTML", $params);
216  }
217
218  // sets the text of a pad
219  public function setText($padID, $text){
220    return $this->post("setText", array(
221      "padID" => $padID,
222      "text"  => $text
223    ));
224  }
225
226  // sets the html text of a pad
227  public function setHTML($padID, $html){
228    return $this->post("setHTML", array(
229      "padID" => $padID,
230      "html"  => $html
231    ));
232  }
233
234  // PAD
235  // Group pads are normal pads, but with the name schema
236  // GROUPID$PADNAME. A security manager controls access of them and its
237  // forbidden for normal pads to include a $ in the name.
238
239  // creates a new pad
240  public function createPad($padID, $text){
241    return $this->post("createPad", array(
242      "padID" => $padID,
243      "text"  => $text
244    ), 'POST');
245  }
246
247  // returns the number of revisions of this pad
248  public function getRevisionsCount($padID){
249    return $this->get("getRevisionsCount", array(
250      "padID" => $padID
251    ));
252  }
253
254  // deletes a pad
255  public function deletePad($padID){
256    return $this->post("deletePad", array(
257      "padID" => $padID
258    ));
259  }
260
261  // returns the read only link of a pad
262  public function getReadOnlyID($padID){
263    return $this->get("getReadOnlyID", array(
264      "padID" => $padID
265    ));
266  }
267
268  // sets a boolean for the public status of a pad
269  public function setPublicStatus($padID, $publicStatus){
270    if (is_bool($publicStatus)) {
271      $publicStatus = $publicStatus ? "true" : "false";
272    }
273    return $this->post("setPublicStatus", array(
274      "padID"        => $padID,
275      "publicStatus" => $publicStatus
276    ));
277  }
278
279  // return true of false
280  public function getPublicStatus($padID){
281    return $this->get("getPublicStatus", array(
282      "padID" => $padID
283    ));
284  }
285
286  // returns ok or a error message
287  public function setPassword($padID, $password){
288    return $this->post("setPassword", array(
289      "padID"    => $padID,
290      "password" => $password
291    ));
292  }
293
294  // returns true or false
295  public function isPasswordProtected($padID){
296    return $this->get("isPasswordProtected", array(
297      "padID" => $padID
298    ));
299  }
300}
301
302