├── License.txt ├── SharePointAPI.php ├── composer.json ├── readme.md └── src └── Thybag ├── Auth ├── SharePointOnlineAuth.php ├── SoapClientAuth.php └── StreamWrapperHttpAuth.php ├── Service ├── ListService.php └── QueryObjectService.php └── SharePointAPI.php /License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Carl Saggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /SharePointAPI.php: -------------------------------------------------------------------------------- 1 | ', '', ''); 34 | 35 | If your installation requires NTLM Authentication, you can instead use: 36 | 37 | use Thybag\SharePointAPI; 38 | $sp = new SharePointAPI('', '', '', 'NTLM'); 39 | 40 | SharePoint Online users must use: 41 | 42 | use Thybag\SharePointAPI; 43 | $sp = new SharePointAPI('', '', '', 'SPONLINE'); 44 | 45 | 46 | All methods return an Array by default. `SetReturnType` can be used to specify that results should be returned as objects instead. 47 | 48 | #### Reading from a List. 49 | 50 | ###### To return all items from a list use either 51 | 52 | $sp->read(''); 53 | 54 | or 55 | 56 | $sp->query('')->get(); 57 | 58 | 59 | ###### To return only the first 10 items from a list use: 60 | 61 | $sp->read('', 10); 62 | 63 | or 64 | 65 | $sp->query('')->limit(10)->get(); 66 | 67 | 68 | ###### To return all the items from a list where surname is smith use: 69 | 70 | $sp->read('', NULL, array('surname'=>'smith')); 71 | 72 | or 73 | 74 | $sp->query('')->where('surname', '=', 'smith')->get(); 75 | 76 | 77 | ###### To return the first 5 items where the surname is smith and the age is 40 78 | 79 | $sp->read('', 5, array('surname'=>'smith','age'=>40)); 80 | 81 | or 82 | 83 | $sp->query('')->where('surname', '=', 'smith')->and_where('age', '=', '40')->limit(5)->get(); 84 | 85 | 86 | 87 | ###### To return the first 10 items where the surname is "smith" using a particular view, call: (It appears views can only be referenced by their GUID) 88 | 89 | $sp->read('', 10, array('surname'=>'smith','age'=>40),'{0FAKE-GUID001-1001001-10001}'); 90 | 91 | or 92 | 93 | $sp->query('')->where('surname', '=', 'smith')->and_where('age', '=', '40')->limit(10)->using('{0FAKE-GUID001-1001001-10001}')->get(); 94 | 95 | 96 | ###### To return the first 10 items where the surname is smith, ordered by age use: 97 | 98 | $sp->read('', 10, array('surname'=>'smith'), NULL, array('age' => 'desc')); 99 | 100 | or 101 | 102 | $sp->query('')->where('surname', '=', 'smith')->limit(10)->sort('age','DESC')->get(); 103 | 104 | 105 | ###### To return the first 5 items, including the columns "favroite_cake" and "favorite animal" 106 | 107 | $sp->read('', 5, NULL, array("favroite_cake", "favorite_animal")); 108 | 109 | or 110 | 111 | $sp->query('')->fields(array("favroite_cake", "favorite_animal")->limit(5)->get(); 112 | 113 | 114 | By default list item's are returned as arrays with lower case index's. If you would prefer the results to return as object's, before invoking any read operations use: 115 | 116 | $sp->setReturnType('object'); 117 | 118 | Automatically making the attribute names lowercase can also be deactivated by using: 119 | 120 | $sp->lowercaseIndexs(FALSE); 121 | 122 | 123 | #### Querying a list 124 | The query method can be used when you need to specify a query that is to complex to be easily defined using the read methods. Queries are constructed using a number of (hopefully expressive) pseudo SQL methods. 125 | 126 | If you for example wanted to query a list of pets and return all dogs below the age of 5 (sorted by age) you could use. 127 | 128 | $sp->query('list of pets')->where('type','=','dog')->and_where('age','<','5')->sort('age','ASC')->get(); 129 | 130 | If you wanted to get the first 10 pets that were either cats or hamsters you could use: 131 | 132 | $sp->query('list of pets')->where('type','=','cat')->or_where('type','=','hamster')->limit(10)->get(); 133 | 134 | If you need to return 5 items, but including all fields contained in a list, you can use. (pass false to all_fields to include hidden fields). 135 | 136 | $sp->query('list of pets')->all_fields()->get(); 137 | 138 | If you have a set of CAML for a specific advanced query you would like to run, you can pass it to the query object using: 139 | 140 | $sp->query('list of pets')->raw_where('Hello World')->limit(10)->get(); 141 | 142 | 143 | #### Adding to a list 144 | 145 | To add a new item to a list you can use either the method "write", "add" or "insert" (all function identically). Creating a new record in a List with the columns forename, surname, age and phone may look like: 146 | 147 | $sp->write('', array('forename'=>'Bob','surname' =>'Smith', 'age'=>40, 'phone'=>'(00000) 000000' )); 148 | 149 | You can also run multiple write operations together by using: 150 | 151 | $sp->writeMultiple('', array( 152 | array('forename' => 'James'), 153 | array('forename' => 'Steve') 154 | )); 155 | 156 | #### Editing Rows 157 | 158 | To edit a row you need to have its ID. Assuming the above row had the ID 5, we could change Bob's name to James with: 159 | 160 | $sp->update('','5', array('forename'=>'James')); 161 | 162 | As with the write method you can also run multiple update operations together by using: 163 | 164 | $sp->updateMultiple('', array( 165 | array('ID'=>5,'job'=>'Intern'), 166 | array('ID'=>6,'job'=>'Intern') 167 | )); 168 | 169 | When using updateMultiple every item MUST have an ID. 170 | 171 | > :heavy_exclamation_mark: This method returns the row that has been updated. It does not always return the updated data, as SharePoint can take longer to update than this method takes to run. 172 | > It is therefore not recommended to use this as a check to ensure a successful update. 173 | 174 | #### Deleting Rows 175 | 176 | In order to delete a row, an ID as well as list name is required. To remove the record for James with the ID 5 you would use: 177 | 178 | $sp->delete('', '5'); 179 | 180 | If you wished to delete a number of records at once, an array of ID's can also be passed to the delete multiple method 181 | 182 | $sp->deleteMultiple('', array('6','7','8')); 183 | 184 | #### CRUD - Create, Read, Update and Delete 185 | The above actions can also be performed using the CRUD wrapper on a list. This may be useful when you 186 | want to perform multiple actions on the same list. Crud methods do not require a list name to be passed in. 187 | 188 | $list = $sp->CRUD(''); 189 | $list->read(10); 190 | $list->create(array( 'id'=>1, 'name'=>'Fred' )); 191 | 192 | #### List all Lists. 193 | You can get a full listing of all available lists within the connected SharePoint subsite by calling: 194 | 195 | $sp->getLists(); 196 | 197 | #### List metaData. 198 | You can access a lists meta data (Column configuration for example) by calling 199 | 200 | $sp->readListMeta('My List'); 201 | 202 | By default the method will attempt to strip out non-useful columns from the results, but keep "hidden". If you'd like the full results to be returned call: 203 | 204 | $sp->readListMeta('My List',FALSE); 205 | 206 | You can also now ignore "hidden" columns: 207 | 208 | $sp->readListMeta('My List', FALSE, TRUE); 209 | 210 | #### Field history / versions. 211 | If your list is versioned in SharePoint, you can read the versions for a specific field using: 212 | 213 | $sp->getVersions('', '', ''); 214 | 215 | #### Attach a file to a SharePoint list item 216 | Files can be attached to SharePoint list items using: 217 | 218 | $sp->addAttachment('', '', ''); 219 | 220 | 221 | ### Helper methods 222 | 223 | The PHP SharePoint API contains a number of helper methods to make it easier to ensure certain values are in the correct format for some of SharePoints special data types. 224 | 225 | ###### dateTime 226 | 227 | The dataTime method can either be passed a text based date 228 | 229 | $date = \Thybag\SharepointApi::dateTime("2012-12-21"); 230 | 231 | Or a unix timestamp 232 | 233 | $date = \Thybag\SharepointApi::dateTime(time(), true); 234 | 235 | And will return a value which can be stored in to SharePoints DateTime fields without issue. 236 | 237 | ###### Lookup 238 | 239 | The lookup data type in SharePoint is for fields that reference a row in another list. In order to correctly populate these values you will need to know the ID of the row the value needs to reference. 240 | 241 | $value = \Thybag\SharepointApi::lookup('3','Pepperoni Pizza'); 242 | 243 | If you do not know the name/title of the value you are storing the method will work fine with just an ID (which SharePoint will also accept directly) 244 | 245 | $value = \Thybag\SharepointApi::lookup('3'); 246 | 247 | ###### Magic Lookup 248 | 249 | If you are attempting to store a value in a "lookup" data type but for some reason only know the title/name of the item, not its ID, you can use the MagicLookup method to quickly look this value up and return it for you. This method will need to be passed both the items title & the list it is contained within. 250 | 251 | $sp->magicLookup("Pepperoni Pizza", "Pizza List"); 252 | 253 | ## Trouble shooting 254 | 255 | * Unable to find the wrapper "https" 256 | 257 | If you are getting this error it normally means that php_openssl (needed to curl https urls) is not enabled on your webserver. With many local websevers (such as XAMPP) you can simply open your php.ini file and uncomment the php_openssl line (ie. remove the ; before it). 258 | 259 | Note: If you are using SharePoint Online and having SSL errors, please pull the latest version which has changed from SSL v3 to TLS for sharepoint online connections - 260 | 261 | Add this line to your `composer.json` file. 262 | 263 | "thybag/php-sharepoint-lists-api": "dev-develop" 264 | 265 | -------------------------------------------------------------------------------- /src/Thybag/Auth/SharePointOnlineAuth.php: -------------------------------------------------------------------------------- 1 | login = $options['login']; 28 | } 29 | if (isset($options['password'])) { 30 | $this->password = $options['password']; 31 | } 32 | } 33 | 34 | // Override do request method 35 | public function __doRequest($request, $location, $action, $version, $one_way = false): ?string { 36 | 37 | // Authenticate with SP online in order to get required authentication cookies 38 | if (!$this->authCookies) $this->configureAuthCookies($location); 39 | 40 | // Set base headers 41 | $headers = array(); 42 | $headers[] = "Content-Type: text/xml; charset=utf-8"; 43 | $headers[] = "SOAPAction: \"{$action}\""; 44 | 45 | $curl = curl_init($location); 46 | 47 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); 48 | curl_setopt($curl, CURLOPT_POST, TRUE); 49 | 50 | // Send request and auth cookies. 51 | curl_setopt($curl, CURLOPT_POSTFIELDS, $request); 52 | curl_setopt($curl, CURLOPT_COOKIE, $this->authCookies); 53 | 54 | curl_setopt($curl, CURLOPT_TIMEOUT, 10); 55 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); 56 | 57 | // Useful for debugging 58 | curl_setopt($curl, CURLOPT_VERBOSE,FALSE); 59 | curl_setopt($curl, CURLOPT_HEADER, FALSE); 60 | 61 | // Add headers 62 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 63 | 64 | // Init the cURL 65 | $response = curl_exec($curl); 66 | 67 | // Throw exceptions if there are any issues 68 | if (curl_errno($curl)) throw new \SoapFault('Receiver', curl_error($curl)); 69 | if ($response == '') throw new \SoapFault('Receiver', "No XML returned"); 70 | 71 | // Close CURL 72 | curl_close($curl); 73 | 74 | // Return? 75 | if (!$one_way) return ($response); 76 | } 77 | 78 | /** 79 | * ConfigureAuthCookies 80 | * Authenticate with sharepoint online in order to get valid authentication cookies 81 | * 82 | * @param $location - Url of sharepoint list 83 | * 84 | * More info on method: 85 | * @see http://allthatjs.com/2012/03/28/remote-authentication-in-sharepoint-online/ 86 | * @see http://macfoo.wordpress.com/2012/06/23/how-to-log-into-office365-or-sharepoint-online-using-php/ 87 | */ 88 | protected function configureAuthCookies($location) { 89 | 90 | // Get endpoint "https://somthing.sharepoint.com" 91 | $location = parse_url($location); 92 | $endpoint = 'https://'.$location['host']; 93 | 94 | // Create XML security token request 95 | $xml = $this->generateSecurityToken($this->login, $this->password, $endpoint); 96 | 97 | // Send request and grab returned xml 98 | $result = $this->authCurl("https://login.microsoftonline.com/extSTS.srf", $xml); 99 | 100 | 101 | // Extract security token from XML 102 | $xml = new \DOMDocument(); 103 | $xml->loadXML($result); 104 | $xpath = new \DOMXPath($xml); 105 | 106 | // Register SOAPFault namespace for error checking 107 | $xpath->registerNamespace('psf', "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault"); 108 | 109 | // Try to detect authentication errors 110 | $errors = $xpath->query("//psf:internalerror"); 111 | if($errors->length > 0){ 112 | $info = $errors->item(0)->childNodes; 113 | throw new \Exception($info->item(1)->nodeValue, $info->item(0)->nodeValue); 114 | } 115 | 116 | $nodelist = $xpath->query("//wsse:BinarySecurityToken"); 117 | foreach ($nodelist as $n){ 118 | $token = $n->nodeValue; 119 | break; 120 | } 121 | 122 | if(!isset($token)){ 123 | throw new \Exception("Unable to extract token from authentiction request"); 124 | } 125 | 126 | // Send token to SharePoint online in order to gain authentication cookies 127 | $result = $this->authCurl($endpoint."/_forms/default.aspx?wa=wsignin1.0", $token, true); 128 | 129 | // Extract Authentication cookies from response & set them in to AuthCookies var 130 | $this->authCookies = $this->extractAuthCookies($result); 131 | } 132 | 133 | /** 134 | * extractAuthCookies 135 | * Extract Authentication cookies from SP response & format in to usable cookie string 136 | * 137 | * @param $result cURL Response 138 | * @return $cookie_payload string containing cookie data. 139 | */ 140 | protected function extractAuthCookies($result){ 141 | 142 | $authCookies = array(); 143 | $cookie_payload = ''; 144 | 145 | $header_array = explode("\r\n", $result); 146 | 147 | // Get the two auth cookies 148 | foreach($header_array as $header) { 149 | $loop = explode(":",$header); 150 | if (strtolower($loop[0]) == 'set-cookie') { 151 | $authCookies[] = $loop[1]; 152 | } 153 | } 154 | 155 | // Extract cookie name & payload and format in to cURL compatible string 156 | foreach($authCookies as $payload){ 157 | $e = strpos($payload, "="); 158 | // Get name 159 | $name = substr($payload, 0, $e); 160 | // Get token 161 | $content = substr($payload, $e+1); 162 | $content = substr($content, 0, strpos($content, ";")); 163 | 164 | // If not first cookie, add cookie seperator 165 | if($cookie_payload !== '') $cookie_payload .= '; '; 166 | 167 | // Add cookie to string 168 | $cookie_payload .= $name.'='.$content; 169 | } 170 | 171 | return $cookie_payload; 172 | } 173 | 174 | /** 175 | * authCurl 176 | * helper method used to cURL SharePoint Online authentiction webservices 177 | * 178 | * @param $url URL to cURL 179 | * @param $payload value to post to URL 180 | * @param $header true|false - Include headers in response 181 | * @return $raw Data returned from cURL. 182 | */ 183 | protected function authCurl($url, $payload, $header = false){ 184 | $ch = curl_init(); 185 | curl_setopt($ch,CURLOPT_URL,$url); 186 | curl_setopt($ch,CURLOPT_POST,1); 187 | curl_setopt($ch,CURLOPT_POSTFIELDS, $payload); 188 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 189 | 190 | curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); 191 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 192 | curl_setopt($ch, CURLOPT_TIMEOUT, 10); 193 | 194 | if($header) curl_setopt($ch, CURLOPT_HEADER, true); 195 | 196 | $result = curl_exec($ch); 197 | 198 | // catch error 199 | if($result === false) { 200 | throw new \SoapFault('Sender', 'Curl error: ' . curl_error($ch)); 201 | } 202 | 203 | curl_close($ch); 204 | 205 | return $result; 206 | } 207 | 208 | /** 209 | * Get the XML to request the security token 210 | * 211 | * @param string $username 212 | * @param string $password 213 | * @param string $endpoint 214 | * @return type string 215 | */ 216 | protected function generateSecurityToken($username, $password, $endpoint) { 217 | return << 221 | 222 | http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue 223 | 224 | http://www.w3.org/2005/08/addressing/anonymous 225 | 226 | https://login.microsoftonline.com/extSTS.srf 227 | 229 | 230 | $username 231 | $password 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | $endpoint 240 | 241 | 242 | http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey 243 | http://schemas.xmlsoap.org/ws/2005/02/trust/Issue 244 | urn:oasis:names:tc:SAML:1.0:assertion 245 | 246 | 247 | 248 | TOKEN; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/Thybag/Auth/SoapClientAuth.php: -------------------------------------------------------------------------------- 1 | . 25 | */ 26 | 27 | /** 28 | * SoapClientAuth 29 | * The interface and operation of this class is identical to the PHP SoapClient class (http://php.net/manual/en/class.soapclient.php) 30 | * except this class will perform HTTP authentication for both SOAP messages and while downloading WSDL over HTTP and HTTPS. 31 | * Provide the options login and password in the options array of the constructor. 32 | * 33 | * @author tc 34 | * @copyright Copyright (C) 2011 tc software 35 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License 36 | * @link http://php.net/manual/en/class.soapclient.php 37 | * @link http://tcsoftware.net/ 38 | */ 39 | class SoapClientAuth extends \SoapClient { 40 | 41 | public $Username = NULL; 42 | public $Password = NULL; 43 | 44 | /** 45 | * 46 | * @param string $wsdl 47 | * @param array $options 48 | */ 49 | public function __construct($wsdl, $options = NULL) { 50 | $wrappers = stream_get_wrappers(); 51 | 52 | stream_wrapper_unregister('http'); 53 | stream_wrapper_register('http', '\Thybag\Auth\StreamWrapperHttpAuth'); 54 | 55 | if (in_array("https", $wrappers)) { 56 | stream_wrapper_unregister('https'); 57 | stream_wrapper_register('https', '\Thybag\Auth\StreamWrapperHttpAuth'); 58 | } 59 | 60 | if ($options) { 61 | $this->Username = $options['login']; 62 | \Thybag\Auth\StreamWrapperHttpAuth::$Username = $this->Username; 63 | $this->Password = $options['password']; 64 | \Thybag\Auth\StreamWrapperHttpAuth::$Password = $this->Password; 65 | } 66 | 67 | parent::__construct($wsdl, ($options ? $options : array())); 68 | 69 | stream_wrapper_restore('http'); 70 | if (in_array("https", $wrappers)) stream_wrapper_restore('https'); 71 | 72 | } 73 | 74 | /** 75 | * @param string $request 76 | * @param string $location 77 | * @param string $action 78 | * @param int $version 79 | * @param int $one_way 80 | * 81 | * @return mixed|string 82 | * @throws \Exception 83 | */ 84 | public function __doRequest($request, $location, $action, $version, $one_way = 0) { 85 | 86 | $headers = array( 87 | 'User-Agent: PHP-SOAP', 88 | 'Content-Type: text/xml; charset=utf-8', 89 | 'SOAPAction: "' . $action . '"', 90 | 'Expect: 100-continue', 91 | 'Connection: Keep-Alive' 92 | ); 93 | 94 | $this->__last_request_headers = $headers; 95 | $location = $this->sanitizeUrl($location); 96 | 97 | $ch = curl_init($location); 98 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 99 | 100 | curl_setopt($ch, CURLOPT_POST, TRUE); 101 | curl_setopt($ch, CURLOPT_POSTFIELDS, $request); 102 | 103 | curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 104 | curl_setopt($ch, CURLOPT_FAILONERROR, FALSE); 105 | curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 106 | 107 | curl_setopt($ch, CURLOPT_USERPWD, $this->Username . ':' . $this->Password); 108 | curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); 109 | 110 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 111 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 112 | 113 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 114 | 115 | $response = curl_exec($ch); 116 | 117 | if (($info = curl_getinfo($ch)) && $info['http_code'] == 200) { 118 | return $response; 119 | } 120 | else { 121 | if ($info['http_code'] == 401) { 122 | throw new \Exception ('Access Denied', 401); 123 | } 124 | else { 125 | if (curl_errno($ch) != 0) { 126 | throw new \Exception(curl_error($ch), curl_errno($ch)); 127 | } 128 | else { 129 | throw new \Exception('Error', $info['http_code']); 130 | } 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Sanitize URL to request to Sharepoint 137 | * 138 | * @param string $url 139 | * @return type string 140 | */ 141 | protected function sanitizeUrl($url) { 142 | return str_replace(" ", "%20", $url); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Thybag/Auth/StreamWrapperHttpAuth.php: -------------------------------------------------------------------------------- 1 | . 24 | */ 25 | 26 | /** 27 | * Class streamWrapperHttpAuth 28 | * @author tc 29 | * @copyright Copyright (C) 2011 tc software 30 | * 31 | * @package Thybag\Auth 32 | */ 33 | class StreamWrapperHttpAuth { 34 | 35 | public static $Username = NULL; 36 | public static $Password = NULL; 37 | 38 | private $path = NULL; 39 | private $position = 0; 40 | private $buffer = NULL; 41 | private $curlHandle = NULL; 42 | 43 | public function stream_close() { 44 | if ($this->curlHandle) { 45 | curl_close($this->curlHandle); 46 | } 47 | } 48 | 49 | public function stream_open($path, $mode, $options, &$opened_path) { 50 | $this->path = $path; 51 | $response = $this->postRequest($this->path); 52 | $this->buffer = ($response !== FALSE ? $response : NULL); 53 | $this->position = 0; 54 | 55 | return $response !== FALSE; 56 | } 57 | 58 | public function stream_eof() { 59 | return $this->position > strlen($this->buffer); 60 | } 61 | 62 | public function stream_flush() { 63 | $this->position = 0; 64 | $this->buffer = NULL; 65 | } 66 | 67 | public function stream_read($count) { 68 | if ($this->buffer) { 69 | $data = substr($this->buffer, $this->position, $count); 70 | $this->position += $count; 71 | 72 | return $data; 73 | } 74 | 75 | return FALSE; 76 | } 77 | 78 | public function stream_write($data) { 79 | return ($this->buffer ? TRUE : FALSE); 80 | } 81 | 82 | public function stream_seek($offset, $whence = SEEK_SET) { 83 | switch ($whence) { 84 | case SEEK_SET: 85 | $this->position = $offset; 86 | break; 87 | case SEEK_CUR: 88 | $this->position += $offset; 89 | break; 90 | case SEEK_END: 91 | $this->position = strlen($this->buffer) + $offset; 92 | break; 93 | } 94 | 95 | return TRUE; 96 | } 97 | 98 | public function stream_tell() { 99 | return $this->position; 100 | } 101 | 102 | public function stream_stat() { 103 | return array('size' => strlen($this->buffer)); 104 | } 105 | 106 | public function url_stat($path, $flags) { 107 | $response = $this->postRequest($path); 108 | 109 | return array('size' => strlen($response)); 110 | } 111 | 112 | protected function postRequest($path, $authType = CURLAUTH_ANY) { 113 | $this->curlHandle = curl_init($path); 114 | curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, TRUE); 115 | curl_setopt($this->curlHandle, CURLOPT_FOLLOWLOCATION, TRUE); 116 | if (streamWrapperHttpAuth::$Username) { 117 | curl_setopt($this->curlHandle, CURLOPT_HTTPAUTH, $authType); 118 | curl_setopt( 119 | $this->curlHandle, 120 | CURLOPT_USERPWD, 121 | streamWrapperHttpAuth::$Username . ':' . streamWrapperHttpAuth::$Password 122 | ); 123 | } 124 | 125 | curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE); 126 | curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2); 127 | 128 | $response = curl_exec($this->curlHandle); 129 | 130 | if (($info = curl_getinfo($this->curlHandle)) && $info['http_code'] == 200) { 131 | if (curl_errno($this->curlHandle) == 0) { 132 | return $response; 133 | } 134 | else { 135 | throw new \Exception(curl_error($this->curlHandle), curl_errno($this->curlHandle)); 136 | } 137 | } 138 | else { 139 | if ($info['http_code'] == 401) { // Attempt NTLM Auth only, CURLAUTH_ANY does not work with NTML 140 | if ($authType != CURLAUTH_NTLM) { 141 | return $this->postRequest($path, CURLAUTH_NTLM); 142 | } 143 | else { 144 | throw new \Exception ('Access Denied', 401); 145 | } 146 | } 147 | else { 148 | if (curl_errno($this->curlHandle) != 0) { 149 | throw new \Exception(curl_error($this->curlHandle), curl_errno($this->curlHandle)); 150 | } 151 | else { 152 | throw new \Exception('Error', $info['http_code']); 153 | } 154 | } 155 | } 156 | 157 | return FALSE; 158 | } 159 | } -------------------------------------------------------------------------------- /src/Thybag/Service/ListService.php: -------------------------------------------------------------------------------- 1 | api = $api; 32 | $this->list_name = $list_name; 33 | } 34 | 35 | /** 36 | * Create 37 | * Create new item in the List 38 | * 39 | * @param Array $data Assosative array describing data to store 40 | * @return Array 41 | */ 42 | public function create (array $data) { 43 | return $this->api->write($this->list_name, $data); 44 | } 45 | 46 | /** 47 | * createMultiple 48 | * Batch add items to the List 49 | * 50 | * @param Array of arrays of assosative array of data to change. Each item MUST include an ID field. 51 | * @return Array 52 | */ 53 | public function createMultiple (array $data) { 54 | return $this->api->writeMultiple($this->list_name, $data); 55 | } 56 | 57 | /** 58 | * Read 59 | * Read items from List 60 | * 61 | * @param int $limit 62 | * @param Array $query 63 | * @return Array 64 | */ 65 | public function read ($limit = 0, $query = NULL, $view = NULL, $sort = NULL, $options = NULL) { 66 | return $this->api->read($this->list_name, $limit, $query, $view, $sort, $options); 67 | } 68 | 69 | /** 70 | * Update 71 | * Update/Modifiy an existing list item. 72 | * 73 | * @param int $ID ID of item to update 74 | * @param Array $data Assosative array of data to change. 75 | * @return Array 76 | */ 77 | public function update ($item_id, array $data) { 78 | return $this->api->update($this->list_name, $item_id, $data); 79 | } 80 | 81 | /** 82 | * UpdateMultiple 83 | * Batch Update/Modifiy existing list item's. 84 | * 85 | * @param Array of arrays of assosative array of data to change. Each item MUST include an ID field. 86 | * @return Array 87 | */ 88 | public function updateMultiple (array $data) { 89 | return $this->api->updateMultiple($this->list_name, $data); 90 | } 91 | 92 | /** 93 | * Delete 94 | * Delete an existing list item. 95 | * 96 | * @param int $item_id ID of item to delete 97 | * @return Array 98 | */ 99 | public function delete ($item_id, array $data = array()) { 100 | return $this->api->delete($this->list_name, $item_id, $data); 101 | } 102 | 103 | /** 104 | * Query 105 | * Create a query against a list in sharepoint 106 | * 107 | * @return \Thybag\Service\QueryObjectService 108 | */ 109 | public function query () { 110 | return new \Thybag\Service\QueryObjectService($this->list_name, $this->api); 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /src/Thybag/Service/QueryObjectService.php: -------------------------------------------------------------------------------- 1 | list_name = $list_name; 61 | $this->api = $api; 62 | } 63 | 64 | /** 65 | * Where 66 | * Perform initial where action 67 | * 68 | * @param $col column to test 69 | * @param $test comparison type (=,!+,<,>) 70 | * @param $value to test with 71 | * @return $this 72 | */ 73 | public function where ($col, $test, $val) { 74 | return $this->addQueryLine('where', $col, $test, $val); 75 | } 76 | 77 | /** 78 | * And_Where 79 | * Perform additional and where actions 80 | * 81 | * @param $col column to test 82 | * @param $test comparison type (=,!+,<,>) 83 | * @param $value to test with 84 | * @return $this 85 | */ 86 | public function and_where ($col, $test, $val) { 87 | return $this->addQueryLine('and', $col, $test, $val); 88 | } 89 | 90 | /** 91 | * Or_Where 92 | * Perform additional or where actions 93 | * 94 | * @param $col column to test 95 | * @param $test comparison type (=,!+,<,>) 96 | * @param $value to test with 97 | * @return $this 98 | */ 99 | public function or_where ($col, $test, $val) { 100 | return $this->addQueryLine('or', $col, $test, $val); 101 | } 102 | 103 | /** 104 | * raw_where 105 | * Perform query using user provided WHERE CAML (XMl contained between the tags) 106 | * This can be used when a user needs to perform queries to complex to be defined using the standard methods 107 | * 108 | * @param $caml - RAW CAML 109 | * @return $this 110 | * @throws \Exception - Thrown if standard where states are already in use. 111 | */ 112 | public function raw_where ($caml) { 113 | // Ensure standard query builder isn't in use 114 | if($this->where_caml != '') throw \Exception("where_raw cannot be used in conjunction with the standard and_where/or_where functionality"); 115 | $this->where_caml = $caml; 116 | return $this; 117 | } 118 | 119 | /** 120 | * Limit 121 | * Specify maximum amount of items to return. (if not set, default is used.) 122 | * 123 | * @param $limit number of items to return 124 | * @return $this 125 | */ 126 | public function limit ($limit) { 127 | $this->limit = $limit; 128 | return $this; 129 | } 130 | 131 | /** 132 | * Using 133 | * Specify view to use when returning data. 134 | * 135 | * @param $view Name/GUID 136 | * @return $this 137 | */ 138 | public function using ($view) { 139 | $this->view = $view; 140 | return $this; 141 | } 142 | 143 | /** 144 | * Options 145 | * Specify view to use when returning data. 146 | * 147 | * @param String $options "XML string of query options." 148 | * @return $this 149 | */ 150 | public function options($options){ 151 | $this->options = $options; 152 | return $this; 153 | } 154 | 155 | /** 156 | * Sort 157 | * Specify order data should be returned in. 158 | * 159 | * @param $sort_on column to sort on 160 | * @param $order Sort direction 161 | * @return $this 162 | */ 163 | public function sort ($sort_on, $order = 'desc') { 164 | $queryString = ''; 165 | $this->sort_caml = '' . $queryString . ''; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * fields 172 | * array of fields to include in results 173 | * 174 | * @param $fields array 175 | * @return $this 176 | */ 177 | public function fields (array $fields) { 178 | $this->fields = $fields; 179 | return $this; 180 | } 181 | public function columns ($fields) { return $this->fields($fields); } 182 | 183 | /** 184 | * all_fields 185 | * Attempt to include all fields row has within result 186 | * 187 | * @param $exclude_hidden to to false to include hidden fields 188 | * @return $this 189 | */ 190 | public function all_fields($exclude_hidden = true){ 191 | $fields = $this->api->readListMeta($this->list_name, $exclude_hidden); 192 | foreach ($fields as $field) { 193 | $this->fields[] = $field['name']; 194 | } 195 | return $this; 196 | } 197 | public function all_columns($exclude_hidden = true){ return $this->all_fields($exclude_hidden); } 198 | 199 | /** 200 | * get 201 | * Runs the specified query and returns a usable result. 202 | * 203 | * @return Array: SharePoint List Data 204 | */ 205 | public function get ($options = NULL) { 206 | 207 | // String = view, array = specific fields 208 | $view = (sizeof($this->fields) === 0) ? $this->view : $this->fields; 209 | 210 | return $this->api->read($this->list_name, $this->limit, $this, $view, NULL, $this->options); 211 | } 212 | 213 | /** 214 | * addQueryLine 215 | * Generate CAML for where statements 216 | * 217 | * @param $rel Relation AND/OR etc 218 | * @param $col column to test 219 | * @param $test comparison type (=,!+,<,>,like) 220 | * @param $value value to test with 221 | * @return $this 222 | * @throws \Exception Thrown if $test is unrecognized 223 | */ 224 | private function addQueryLine ($rel, $col, $test, $value) { 225 | // Check tests are usable 226 | if (!in_array($test, array('!=', '>=', '<=', '<', '>', '=', 'like'))) { 227 | throw new \Exception('Unrecognized query parameter. Please use <,>,=,>=,<=, != or like'); 228 | } 229 | 230 | // Make sure $rel is lower-case 231 | $rel = strtolower($rel); 232 | 233 | $test = str_replace(array('!=', '>=', '<=', '<', '>', '=', 'like'), array('Neq', 'Geq', 'Leq', 'Lt', 'Gt', 'Eq', 'Contains'), $test); 234 | 235 | // Create caml 236 | $caml = $this->where_caml; 237 | $content = '' . htmlspecialchars($value) . '' . PHP_EOL; 238 | $caml .= '<' . $test . '>' . $content . ''; 239 | 240 | // Attach relations 241 | if ($rel == 'and') { 242 | $this->where_caml = '' . $caml . ''; 243 | } elseif ($rel == 'or') { 244 | $this->where_caml = '' . $caml . ''; 245 | } elseif ($rel == 'where') { 246 | $this->where_caml = $caml; 247 | } 248 | 249 | // return self 250 | return $this; 251 | } 252 | 253 | /** 254 | * getCAML 255 | * Combine and return the raw CAML data for the query operation that has been specified. 256 | * @return CAML Code (XML) 257 | */ 258 | public function getCAML () { 259 | // Start with empty string 260 | $xml = ''; 261 | 262 | // Add where 263 | if (!empty($this->where_caml)) { 264 | $xml = '' . $this->where_caml . ''; 265 | } 266 | 267 | // add sort 268 | if (!empty($this->sort_caml)) { 269 | $xml .= $this->sort_caml; 270 | } 271 | 272 | return $xml; 273 | } 274 | 275 | /** 276 | * getOptionCAML 277 | * Combine and return the raw CAML data for the request options 278 | * @return CAML Code (XML) 279 | */ 280 | public function getOptionCAML () { 281 | 282 | $xml = ''; 283 | 284 | // if fields are specified 285 | if(sizeof($this->fields) > 0) { 286 | $xml .= $this->api->viewFieldsXML($this->fields); 287 | } 288 | 289 | // If view, add to xml. 290 | if($this->view !== NULL) $xml .= '' . $this->view . ''; 291 | 292 | return $xml; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/Thybag/SharePointAPI.php: -------------------------------------------------------------------------------- 1 | ','',''); 20 | * 21 | * Read: 22 | * $sp->read(''); 23 | * $sp->read('', 500); // Return 500 records 24 | * $sp->read('', NULL, array(''=>''); // Filter on col_name = col_value 25 | * $sp->read('', NULL, NULL, '{FAKE-GUID00-0000-000}'); // Return list items with view (specified via GUID) 26 | * $sp->read('', NULL, NULL, NULL, array('col_name'=>'asc|desc')); 27 | * 28 | * Query: 29 | * $sp->query('')->where('type','=','dog')->and_where('age','>','5')->limit(10)->sort('age','asc')->get(); 30 | * 31 | * Write: (insert) 32 | * $sp->write('', array('' => '','' => '')); 33 | * 34 | * WriteMultiple: 35 | * $sp->writeMultiple('', array( 36 | * array('Title' => 'New item'), 37 | * array('Title' => 'New item 2') 38 | * )); 39 | * 40 | * Update: 41 | * $sp->update('','', array(''=>'')); 42 | * 43 | * UpdateMultiple: 44 | * $sp->updateMultiple('', array( 45 | * array('ID'=>1, 'Title' => 'new name'), 46 | * array('ID'=>2, 'Title' => 'New name 2') 47 | * )); 48 | * 49 | * Delete: 50 | * $sp->delete('',''); 51 | * 52 | * CRUD can be used for multiple actions on a single list. 53 | * $list = $api->CRUD(''); 54 | * $list->read(10); 55 | * $list->create(array('' => '','' => '')); 56 | */ 57 | class SharePointAPI { 58 | /** 59 | * Username for SP auth 60 | */ 61 | private $spUsername = ''; 62 | 63 | /** 64 | * Password for SP auth 65 | */ 66 | private $spPassword = ''; 67 | 68 | /** 69 | * Location of WSDL 70 | */ 71 | private $spWsdl = ''; 72 | 73 | /** 74 | * Return type (default: 0) 75 | * 76 | * 0 = Array 77 | * 1 = Object 78 | */ 79 | private $returnType = 0; 80 | 81 | /** 82 | * Make all indexs lower-case 83 | */ 84 | private $lower_case_indexs = TRUE; 85 | 86 | /** 87 | * Maximum rows to return from a List 88 | */ 89 | private $MAX_ROWS = 10000; 90 | 91 | /** 92 | * Place holder for soapClient/SOAP client 93 | */ 94 | private $soapClient = NULL; 95 | 96 | /** 97 | * Whether requests shall be traced 98 | * (compare: http://de.php.net/manual/en/soapclient.soapclient.php ) 99 | */ 100 | protected $soap_trace = TRUE; 101 | 102 | /** 103 | * Whether SOAP errors throw exception of type SoapFault 104 | */ 105 | protected $soap_exceptions = TRUE; 106 | 107 | /** 108 | * Kee-Alive HTTP setting (default: FALSE) 109 | */ 110 | protected $soap_keep_alive = FALSE; 111 | 112 | /** 113 | * SOAP version number (default: SOAP_1_1) 114 | */ 115 | protected $soap_version = SOAP_1_1; 116 | 117 | /** 118 | * Compression 119 | * Example: SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP 120 | */ 121 | protected $soap_compression = 0; 122 | 123 | /** 124 | * Cache behaviour for WSDL content (default: WSDL_CACHE_NONE for better debugging) 125 | */ 126 | protected $soap_cache_wsdl = WSDL_CACHE_NONE; 127 | 128 | /** 129 | * Internal (!) encoding (not SOAP; default: UTF-8) 130 | */ 131 | protected $internal_encoding = 'UTF-8'; 132 | 133 | /** 134 | * Constructor 135 | * 136 | * @param string $spUsername User account to authenticate with. (Must have read/write/edit permissions to given Lists) 137 | * @param string $spPassword Password to use with authenticating account. 138 | * @param string $spWsdl WSDL file for this set of lists ( sharepoint.url/subsite/_vti_bin/Lists.asmx?WSDL ) 139 | * @param string $mode Authenticaton method to use (Defaults to basic auth, also supports SPONLINE & NTLM) 140 | * @param array $options Options for SoapClient 141 | */ 142 | public function __construct ($spUsername, $spPassword, $spWsdl, $mode = 'STANDARD', $options = array()) { 143 | // Check if required class is found 144 | assert(class_exists('SoapClient')); 145 | 146 | // Set data from parameters in this class 147 | $this->spUsername = $spUsername; 148 | $this->spPassword = $spPassword; 149 | $this->spWsdl = $spWsdl; 150 | 151 | /* 152 | * defaults 153 | */ 154 | $defaultOptions = array( 155 | 'trace' => $this->soap_trace, 156 | 'exceptions' => $this->soap_exceptions, 157 | 'keep_alive' => $this->soap_keep_alive, 158 | 'soap_version' => $this->soap_version, 159 | 'cache_wsdl' => $this->soap_cache_wsdl, 160 | 'compression' => $this->soap_compression, 161 | 'encoding' => $this->internal_encoding, 162 | ); 163 | // $options will overwrite defaults if provided 164 | $options = array_merge($defaultOptions, $options); 165 | 166 | // Is login set? 167 | if (!empty($this->spUsername)) { 168 | // Then set login data 169 | $options['login'] = $this->spUsername; 170 | $options['password'] = $this->spPassword; 171 | } 172 | 173 | // Create new SOAP Client 174 | try { 175 | if ((isset($options['login'])) && ($mode == 'NTLM')) { 176 | // If using authentication then use the custom SoapClientAuth class. 177 | $this->soapClient = new \Thybag\Auth\SoapClientAuth($this->spWsdl, $options); 178 | } elseif($mode == 'SPONLINE'){ 179 | $this->soapClient = new \Thybag\Auth\SharePointOnlineAuth($this->spWsdl, $options); 180 | } else { 181 | $this->soapClient = new \SoapClient($this->spWsdl, $options); 182 | } 183 | } catch (\SoapFault $fault) { 184 | // If we are unable to create a Soap Client display a Fatal error. 185 | throw new \Exception('Unable to locate WSDL file. faultcode=' . $fault->getCode() . ',faultstring=' . $fault->getMessage()); 186 | } 187 | } 188 | 189 | /** 190 | * Calls methods on SOAP object 191 | * 192 | * @param string $methodName Name of method to call 193 | * @param array $methodParams Parameters to handle over 194 | * @return mixed $returned Returned values 195 | */ 196 | public final function __call ($methodName, array $methodParams) { 197 | /* 198 | * Is soapClient set? This check may look double here but in later 199 | * developments it might help to trace bugs better and it avoids calls 200 | * on wrong classes if $soapClient got set to something not SoapClient. 201 | */ 202 | if (!$this->soapClient instanceof \SoapClient) { 203 | // Is not set 204 | throw new \Exception('Variable soapClient is not a SoapClient class, have: ' . gettype($this->soapClient), 0xFF); 205 | } 206 | 207 | // Is it a "SOAP callback"? 208 | if (substr($methodName, 0, 2) == '__') { 209 | // Is SoapClient's method 210 | $returned = call_user_func_array(array($this->soapClient, $methodName), $methodParams); 211 | } else { 212 | // Call it 213 | $returned = $this->soapClient->__call($methodName, $methodParams); 214 | } 215 | 216 | // Return any values 217 | return $returned; 218 | } 219 | 220 | /** 221 | * Returns an array of all lists 222 | * 223 | * @param array $keys Keys which shall be included in final JSON output 224 | * @param array $params Only search for lists with given criteria (default: 'hidden' => 'False') 225 | * @param bool $isSensetive Whether to look case-sensetive (default: TRUE) 226 | * @return array $newLists An array with given keys from all lists 227 | */ 228 | public function getLimitedLists (array $keys, array $params = array('hidden' => 'False'), $isSensetive = TRUE) { 229 | // Get the full list back 230 | $lists = $this->getLists(); 231 | 232 | // Init new list and look for all matching entries 233 | $newLists = array(); 234 | foreach ($lists as $entry) { 235 | // Default is found 236 | $isFound = TRUE; 237 | 238 | // Search for all criteria 239 | foreach ($params as $key => $value) { 240 | // Is it found? 241 | if ((isset($entry[$key])) && ((($isSensetive === TRUE) && ($value != $entry[$key])) || (strtolower($value) != strtolower($entry[$key])))) { 242 | // Is not found 243 | $isFound = FALSE; 244 | break; 245 | } 246 | } 247 | 248 | // Add it? 249 | if ($isFound === TRUE) { 250 | // Generate new entry array 251 | $newEntry = array(); 252 | foreach ($keys as $key) { 253 | // Add this key 254 | $newEntry[$key] = $entry[$key]; 255 | } 256 | 257 | // Add this new array 258 | $newLists[] = $newEntry; 259 | unset($newEntry); 260 | } 261 | } 262 | 263 | // Return finished array 264 | return $newLists; 265 | } 266 | 267 | /** 268 | * Get Lists 269 | * Return an array containing all avaiable lists within a given sharepoint subsite. 270 | * Use "set return type" if you wish for this data to be provided as an object. 271 | * 272 | * @return array (array) | array (object) 273 | */ 274 | public function getLists () { 275 | // Query Sharepoint for full listing of it's lists. 276 | $rawXml = ''; 277 | try { 278 | $rawXml = $this->soapClient->GetListCollection()->GetListCollectionResult->any; 279 | } catch (\SoapFault $fault) { 280 | $this->onError($fault); 281 | } 282 | 283 | // Load XML in to DOM document and grab all list items. 284 | $nodes = $this->getArrayFromElementsByTagName($rawXml, 'List'); 285 | 286 | // Format data in to array or object 287 | foreach ($nodes as $counter => $node) { 288 | foreach ($node->attributes as $attribute => $value) { 289 | $idx = ($this->lower_case_indexs) ? strtolower($attribute) : $attribute; 290 | $results[$counter][$idx] = $node->getAttribute($attribute); 291 | } 292 | 293 | // Make object if needed 294 | if ($this->returnType === 1) { 295 | settype($results[$counter], 'object'); 296 | } 297 | } 298 | 299 | // Add error array if stuff goes wrong. 300 | if (!isset($results)) { 301 | $results = array('warning' => 'No data returned.'); 302 | } 303 | 304 | return $results; 305 | } 306 | 307 | /** 308 | * Read List MetaData (Column configurtion) 309 | * Return a full listing of columns and their configurtion options for a given sharepoint list. 310 | * 311 | * @param $list_name Name or GUID of list to return metaData from. 312 | * @param $hideInternal TRUE|FALSE Attempt to hide none useful columns (internal data etc) 313 | * @param $ignoreHiddenAttribute TRUE|FALSE Ignores 'Hidden' attribute if it is set to 'TRUE' - DEBUG ONLY!!! 314 | * @return Array 315 | */ 316 | public function readListMeta ($list_name, $hideInternal = TRUE, $ignoreHiddenAttribute = FALSE) { 317 | // Ready XML 318 | $CAML = ' 319 | 320 | ' . $list_name . ' 321 | 322 | '; 323 | 324 | // Attempt to query Sharepoint 325 | $rawXml = ''; 326 | try { 327 | $rawXml = $this->soapClient->GetList(new \SoapVar($CAML, XSD_ANYXML))->GetListResult->any; 328 | } catch (\SoapFault $fault) { 329 | $this->onError($fault); 330 | } 331 | 332 | // Load XML in to DOM document and grab all Fields 333 | $nodes = $this->getArrayFromElementsByTagName($rawXml, 'Field'); 334 | 335 | // Format data in to array or object 336 | foreach ($nodes as $counter => $node) { 337 | // Attempt to hide none useful feilds (disable by setting second param to FALSE) 338 | if ($hideInternal && ($node->getAttribute('Type') == 'Lookup' || $node->getAttribute('Type') == 'Computed' || ($node->getAttribute('Hidden') == 'TRUE' && $ignoreHiddenAttribute === FALSE))) { 339 | continue; 340 | } 341 | 342 | // Get Attributes 343 | foreach ($node->attributes as $attribute => $value) { 344 | $idx = ($this->lower_case_indexs) ? strtolower($attribute) : $attribute; 345 | $results[$counter][$idx] = $node->getAttribute($attribute); 346 | } 347 | 348 | // Make object if needed 349 | if ($this->returnType === 1) { 350 | settype($results[$counter], 'object'); 351 | } 352 | 353 | // If hiding internal is enabled and 'id' is not set, remove this element 354 | if ($hideInternal && !isset($results[$counter]['id'])) { 355 | // Then it has to be an "internal" 356 | unset($results[$counter]); 357 | } 358 | } 359 | 360 | // Add error array if stuff goes wrong. 361 | if (!isset($results)) { 362 | $results = array('warning' => 'No data returned.'); 363 | } 364 | 365 | // Return a XML as nice clean Array or Object 366 | return $results; 367 | } 368 | 369 | /** 370 | * Read 371 | * Use's raw CAML to query sharepoint data 372 | * 373 | * @param String $list_name 374 | * @param int $limit 375 | * @param Array $query 376 | * @param String (GUID) $view "View to display results with." 377 | * @param Array $sort 378 | * @param String $options "XML string of query options." 379 | * 380 | * @return Array 381 | */ 382 | public function read ($list_name, $limit = NULL, $query = NULL, $view = NULL, $sort = NULL, $options = NULL) { 383 | // Check limit is set 384 | if ($limit < 1 || is_null($limit)) { 385 | $limit = $this->MAX_ROWS; 386 | } 387 | 388 | // Create Query XML is query is being used 389 | $xml_options = ''; 390 | $xml_query = ''; 391 | $fields_xml = ''; 392 | 393 | // Setup Options 394 | if ($query instanceof Service\QueryObjectService) { 395 | $xml_query = $query->getCAML(); 396 | $xml_options = $query->getOptionCAML(); 397 | } else { 398 | 399 | if (!is_null($query)) { 400 | $xml_query .= $this->whereXML($query); // Build Query 401 | } 402 | if (!is_null($sort)) { 403 | $xml_query .= $this->sortXML($sort);// add sort 404 | } 405 | 406 | // Add view or fields 407 | if (!is_null($view)){ 408 | // array, fields have been specified 409 | if(is_array($view)){ 410 | $xml_options .= $this->viewFieldsXML($view); 411 | }else{ 412 | $xml_options .= '' . $view . ''; 413 | } 414 | } 415 | } 416 | 417 | // If query is required 418 | if (!empty($xml_query)) { 419 | $xml_options .= '' . $xml_query . ''; 420 | } 421 | 422 | /* 423 | * Setup basic XML for querying a SharePoint list. 424 | * If rowLimit is not provided SharePoint will default to a limit of 100 items. 425 | */ 426 | $CAML = ' 427 | 428 | ' . $list_name . ' 429 | ' . $limit . ' 430 | ' . $xml_options . ' 431 | 432 | 433 | ' . $options . ' 434 | 435 | 436 | '; 437 | 438 | // Ready XML 439 | $xmlvar = new \SoapVar($CAML, XSD_ANYXML); 440 | $result = NULL; 441 | 442 | // Attempt to query SharePoint 443 | try { 444 | $result = $this->xmlHandler($this->soapClient->GetListItems($xmlvar)->GetListItemsResult->any); 445 | } catch (\SoapFault $fault) { 446 | $this->onError($fault); 447 | } 448 | 449 | // Return a XML as nice clean Array 450 | return $result; 451 | } 452 | 453 | /** 454 | * ReadFromFolder 455 | * Uses raw CAML to query sharepoint data from a folder 456 | * 457 | * @param String $listName 458 | * @param String $folderName 459 | * @param bool $isLibrary 460 | * @param String $limit 461 | * @param String $query 462 | * @param String $view 463 | * @param String $sort 464 | * 465 | * @return Array 466 | */ 467 | public function readFromFolder($listName, $folderName = '', $isLibrary = false, $limit = NULL, $query = NULL, $view = NULL, $sort = NULL) { 468 | return $this->read($listName, $limit, $query, $view, $sort, "" . ($isLibrary ? '' : 'Lists/') . $listName . '/' . $folderName . "" ); 469 | } 470 | 471 | /** 472 | * Write 473 | * Create new item in a sharepoint list 474 | * 475 | * @param String $list_name Name of List 476 | * @param Array $data Associative array describing data to store 477 | * @return Array 478 | */ 479 | public function write ($list_name, array $data) { 480 | return $this->writeMultiple($list_name, array($data)); 481 | } 482 | 483 | /** 484 | * WriteToFolder 485 | * Create new item in a sharepoint list 486 | * 487 | * @param String $list_name Name of List 488 | * @param String $folderPath Path of folder 489 | * @param Array $data Associative array describing data to store 490 | * @return Array 491 | */ 492 | public function writeToFolder ($list_name, $folderPath, array $data) { 493 | return $this->writeMultipleToFolder($list_name, $folderPath, array($data)); 494 | } 495 | 496 | // Alias (Identical to above) 497 | public function add ($list_name, array $data) { return $this->write($list_name, $data); } 498 | public function insert ($list_name, array $data) { return $this->write($list_name, $data); } 499 | 500 | /** 501 | * WriteMultiple 502 | * Batch create new items in a sharepoint list 503 | * 504 | * @param String $list_name Name of List 505 | * @param Array of arrays Associative array's describing data to store 506 | * @return Array 507 | */ 508 | public function writeMultiple ($list_name, array $items) { 509 | return $this->modifyList($list_name, $items, 'New'); 510 | } 511 | 512 | /** 513 | * WriteMultipleToFolder 514 | * Batch create new items in a sharepoint list and place them in specified folder 515 | * 516 | * @param String $list_name Name of List 517 | * @param String $folderPath Path of folder 518 | * @param Array of arrays Associative array's describing data to store 519 | * @return Array 520 | */ 521 | public function writeMultipleToFolder ($list_name, $folderPath, array $items) { 522 | return $this->modifyList($list_name, $items, 'New', $folderPath); 523 | } 524 | 525 | // Alias (Identical to above) 526 | public function addMultiple ($list_name, array $items) { return $this->writeMultiple($list_name, $items); } 527 | public function insertMultiple ($list_name, array $items) { return $this->writeMultiple($list_name, $items); } 528 | 529 | /** 530 | * Update 531 | * Update/Modify an existing list item. 532 | * 533 | * @param String $list_name Name of list 534 | * @param int $ID ID of item to update 535 | * @param Array $data Associative array of data to change. 536 | * @return Array 537 | */ 538 | public function update ($list_name, $ID, array $data) { 539 | // Add ID to item 540 | $data['ID'] = $ID; 541 | return $this->updateMultiple($list_name, array($data)); 542 | } 543 | 544 | // aliases 545 | public function edit($list_name, $ID, array $data) { return $this->update ($list_name, $ID, $data); } 546 | 547 | /** 548 | * UpdateMultiple 549 | * Batch Update/Modify existing list item's. 550 | * 551 | * @param String $list_name Name of list 552 | * @param Array of arrays of assosative array of data to change. Each item MUST include an ID field. 553 | * @return Array 554 | */ 555 | public function updateMultiple ($list_name, array $items) { 556 | return $this->modifyList($list_name, $items, 'Update'); 557 | } 558 | 559 | // aliases 560 | public function editMultiple($list_name, array $items) { return $this->updateMultiple ($list_name, $items); } 561 | 562 | /** 563 | * Delete 564 | * Delete an existing list item. 565 | * 566 | * @param String $list_name Name of list 567 | * @param int $ID ID of item to delete 568 | * @param array $data An array of additional required key/value pairs for the item to delete e.g. FileRef => URL to file. 569 | * @return Array 570 | */ 571 | public function delete ($list_name, $ID, array $data = array()) { 572 | return $this->deleteMultiple($list_name, array($ID), array($ID => $data)); 573 | } 574 | 575 | /** 576 | * DeleteMultiple 577 | * Delete existing multiple list items. 578 | * 579 | * @param String $list_name Name of list 580 | * @param array $IDs IDs of items to delete 581 | * @param array $data An array of arrays of additional required key/value pairs for each item to delete e.g. FileRef => URL to file. 582 | * @return Array 583 | */ 584 | public function deleteMultiple ($list_name, array $IDs, array $data = array()) { 585 | /* 586 | * change input "array(ID1, ID2, ID3)" to "array(array('id' => ID1), 587 | * array('id' => ID2), array('id' => ID3))" in order to be compatible 588 | * with modifyList. 589 | * 590 | * For each ID also check if we have any additional data. If so then 591 | * add it to the delete data. 592 | */ 593 | $deletes = array(); 594 | foreach ($IDs as $ID) { 595 | $delete = array('ID' => $ID); 596 | // Add additional data if available 597 | if (!empty($data[$ID])) { 598 | foreach ($data[$ID] as $key => $value) { 599 | $delete[$key] = $value; 600 | } 601 | } 602 | $deletes[] = $delete; 603 | } 604 | 605 | // Return a XML as nice clean Array 606 | return $this->modifyList($list_name, $deletes, 'Delete'); 607 | } 608 | 609 | /** 610 | * addAttachment 611 | * Add an attachment to a SharePoint List 612 | * 613 | * @param $list_name Name of list 614 | * @param $list_item_id ID of record to attach attachment to 615 | * @param $file_name path of file to attach 616 | * @return Array 617 | */ 618 | public function addAttachment ($list_name, $list_item_id, $file_name) { 619 | // base64 encode file 620 | $attachment = base64_encode(file_get_contents($file_name)); 621 | 622 | // Wrap in CAML 623 | $CAML = ' 624 | 625 | ' . $list_name . ' 626 | ' . $list_item_id . ' 627 | ' . $file_name . ' 628 | ' . $attachment . ' 629 | '; 630 | 631 | $xmlvar = new \SoapVar($CAML, XSD_ANYXML); 632 | 633 | // Attempt to run operation 634 | try { 635 | $this->soapClient->AddAttachment($xmlvar); 636 | } catch (\SoapFault $fault) { 637 | $this->onError($fault); 638 | } 639 | 640 | // Return true on success 641 | return true; 642 | } 643 | 644 | /** 645 | * deleteAttachment 646 | * Remove an attachment from a SharePoint list item 647 | * 648 | * @param $list_name Name of list 649 | * @param $list_item_id ID of record item is attached to 650 | * @param $url 651 | */ 652 | public function deleteAttachment ($list_name, $list_item_id, $url) { 653 | // Wrap in CAML 654 | $CAML = ' 655 | 656 | ' . $list_name . ' 657 | ' . $list_item_id . ' 658 | ' . $url . ' 659 | '; 660 | 661 | $xmlvar = new \SoapVar($CAML, XSD_ANYXML); 662 | 663 | // Attempt to run operation 664 | try { 665 | $this->soapClient->DeleteAttachment($xmlvar); 666 | } catch (\SoapFault $fault) { 667 | $this->onError($fault); 668 | } 669 | 670 | // Return true on success 671 | return true; 672 | } 673 | 674 | /** 675 | * getAttachment 676 | * Return an attachment from a SharePoint list item 677 | * 678 | * @param $list_name Name of list 679 | * @param $list_item_id ID of record item is attached to 680 | * @return Array of attachment urls 681 | */ 682 | public function getAttachments ($list_name, $list_item_id) { 683 | // Wrap in CAML 684 | $CAML = ' 685 | 686 | ' . $list_name . ' 687 | ' . $list_item_id . ' 688 | '; 689 | 690 | $xmlvar = new \SoapVar($CAML, XSD_ANYXML); 691 | 692 | // Attempt to run operation 693 | try { 694 | $rawXml = $this->soapClient->GetAttachmentCollection($xmlvar)->GetAttachmentCollectionResult->any; 695 | } catch (\SoapFault $fault) { 696 | $this->onError($fault); 697 | } 698 | 699 | // Load XML in to DOM document and grab all list items. 700 | $nodes = $this->getArrayFromElementsByTagName($rawXml, 'Attachment'); 701 | 702 | $attachments = array(); 703 | 704 | // Format data in to array or object 705 | foreach ($nodes as $counter => $node) { 706 | $attachments[] = $node->textContent; 707 | } 708 | 709 | // Return Array of attachment URLs 710 | return $attachments; 711 | } 712 | 713 | /** 714 | * setReturnType 715 | * Change the dataType used to store List items. 716 | * Array or Object. 717 | * 718 | * @param $type 719 | */ 720 | public function setReturnType ($type) { 721 | if (trim(strtolower($type)) == 'object') { 722 | $this->returnType = 1; 723 | } else { 724 | $this->returnType = 0; 725 | } 726 | } 727 | 728 | /** 729 | * lowercaseIndexs 730 | * Enable or disable automatically lowercasing index's for returned data. 731 | * (By default this is enabled to avoid users having to worry about the case attributes are in) 732 | * Array or Object. 733 | * 734 | * @param $enable TRUE|FALSE 735 | */ 736 | public function lowercaseIndexs ($enable) { 737 | $this->lower_case_indexs = ($enable === TRUE); 738 | } 739 | 740 | /** 741 | * Query 742 | * Create a query against a list in sharepoint 743 | * 744 | * Build query's as $sp->query('my_list')->where('score','>',15)->and_where('year','=','9')->get(); 745 | * 746 | * @param List name / GUID number 747 | * @return \Thybag\Service\QueryObjectService 748 | */ 749 | public function query ($table) { 750 | return new \Thybag\Service\QueryObjectService($table, $this); 751 | } 752 | 753 | /** 754 | * CRUD 755 | * Create a simple Create, Read, Update, Delete Wrapper around a specific list. 756 | * 757 | * @param $list_name Name of list to provide CRUD for. 758 | * @return \Thybag\Service\ListService 759 | */ 760 | public function CRUD ($list_name) { 761 | return new \Thybag\Service\ListService($list_name, $this); 762 | } 763 | 764 | /** 765 | * "Getter" for an array of nodes from given "raw XML" and tag name 766 | * 767 | * @param string $rawXml "Raw XML" data 768 | * @param string $tag Name of tag 769 | * @param string $namespace Optional namespace 770 | * @return array $nodes An array of XML nodes 771 | */ 772 | private function getArrayFromElementsByTagName ($rawXml, $tag, $namespace = NULL) { 773 | // Get DOM instance and load XML 774 | $dom = new \DOMDocument(); 775 | 776 | $dom->loadXML($rawXml, (LIBXML_VERSION >= 20900) ? LIBXML_PARSEHUGE : null); 777 | 778 | // Is namespace set? 779 | if (!is_null($namespace)) { 780 | // Use it 781 | $nodes = $dom->getElementsByTagNameNS($tag, $namespace); 782 | } else { 783 | // Get nodes 784 | $nodes = $dom->getElementsByTagName($tag); 785 | } 786 | 787 | // Return nodes list 788 | return $nodes; 789 | } 790 | 791 | /** 792 | * xmlHandler 793 | * Transform the XML returned from SOAP in to a useful data structure. 794 | * By Default all sharepoint items will be represented as arrays. 795 | * Use setReturnType('object') to have them returned as objects. 796 | * 797 | * @param $rawXml XML DATA returned by SOAP 798 | * @return Array( Array ) | Array( Object ) 799 | */ 800 | private function xmlHandler ($rawXml) { 801 | // Use DOMDocument to proccess XML 802 | $results = $this->getArrayFromElementsByTagName($rawXml, '#RowsetSchema', '*'); 803 | $resultArray = array(); 804 | 805 | // Proccess Object and return a nice clean associative array of the results 806 | foreach ($results as $i => $result) { 807 | $resultArray[$i] = array(); 808 | foreach ($result->attributes as $attribute => $value) { 809 | $idx = ($this->lower_case_indexs) ? strtolower($attribute) : $attribute; 810 | // Re-assign all the attributes into an easy to access array 811 | $resultArray[$i][str_replace('ows_', '', $idx)] = $result->getAttribute($attribute); 812 | } 813 | 814 | /* 815 | * ReturnType 1 = Object. 816 | * If set, change array in to an object. 817 | * 818 | * Feature based on implementation by dcarbone (See: https://github.com/dcarbone/ ) 819 | */ 820 | if ($this->returnType === 1) { 821 | settype($resultArray[$i], 'object'); 822 | } 823 | } 824 | 825 | // Check some values were actually returned 826 | if (count($resultArray) == 0) { 827 | $resultArray = array( 828 | 'warning' => 'No data returned.', 829 | 'raw_xml' => $rawXml 830 | ); 831 | } 832 | 833 | return $resultArray; 834 | } 835 | 836 | /** 837 | * Query XML 838 | * Generates XML for WHERE Query 839 | * 840 | * @param Array $q array('' => '') 841 | * @return XML DATA 842 | */ 843 | private function whereXML (array $q) { 844 | $queryString = ''; 845 | $counter = 0; 846 | 847 | foreach ($q as $col => $value) { 848 | $counter++; 849 | $queryString .= '' . htmlspecialchars($value) . ''; 850 | 851 | // Add additional "and"s if there are multiple query levels needed. 852 | if ($counter >= 2) { 853 | $queryString = '' . $queryString . ''; 854 | } 855 | } 856 | 857 | return '' . $queryString . ''; 858 | } 859 | 860 | /** 861 | * "Getter" for sort ascending ("true") or descending ("false") from given value 862 | * 863 | * @param string $value Value to be checked 864 | * @return string $sort "true" for ascending, "false" (default) for descending 865 | */ 866 | public function getSortFromValue ($value) { 867 | // Make all lower-case 868 | $value = strtolower($value); 869 | 870 | // Default is descending 871 | $sort = 'FALSE'; 872 | 873 | // Is value set to allow ascending sorting? 874 | if ($value == 'asc' || $value == 'true' || $value == 'ascending') { 875 | // Sort ascending 876 | $sort = 'TRUE'; 877 | } 878 | 879 | // Return it 880 | return $sort; 881 | } 882 | 883 | /** 884 | * Sort XML 885 | * Generates XML for sort 886 | * 887 | * @param Array $sort array('' => 'asc | desc') 888 | * @return XML DATA 889 | */ 890 | private function sortXML (array $sort) { 891 | // On no count, no need to sort 892 | if (count($sort) == 0) { 893 | return ''; 894 | } 895 | 896 | $queryString = ''; 897 | foreach ($sort as $col => $value) { 898 | $queryString .= ''; 899 | } 900 | return '' . $queryString . ''; 901 | } 902 | 903 | /** 904 | * view Field sXML 905 | * Generates XML for specifying fields to return 906 | * 907 | * @param Array $fields to include 908 | * @return XML DATA 909 | */ 910 | public function viewFieldsXML(array $fields){ 911 | $xml = ''; 912 | // Convert fields to array 913 | foreach($fields as $field){ 914 | $xml .= ''; 915 | } 916 | // wrap tags 917 | return ''.$xml.''; 918 | } 919 | 920 | /** 921 | * modifyList 922 | * Perform an action on a sharePoint list to either update or add content to it. 923 | * This method will use prepBatch to generate the batch xml, then call the SharePoint SOAP API with this data 924 | * to apply the changes. 925 | * 926 | * @param $list_name SharePoint List to update 927 | * @param $items Array of new items or item changesets. 928 | * @param $method New/Update/Delete 929 | * @return Array|Object 930 | */ 931 | public function modifyList ($list_name, array $items, $method, $folderPath = null) { 932 | // Get batch XML 933 | $commands = $this->prepBatch($items, $method); 934 | 935 | $rootFolderAttr = ''; 936 | if($folderPath != null && $folderPath != '/') { 937 | $sitePath = substr($this->spWsdl, 0, strpos($this->spWsdl, '_vti_bin')); 938 | $rootFolderAttr = ' RootFolder="'.$sitePath.$list_name.'/'.$folderPath.'"'; 939 | } 940 | 941 | // Wrap in CAML 942 | $CAML = ' 943 | 944 | ' . $list_name . ' 945 | 946 | 947 | ' . $commands . ' 948 | 949 | 950 | '; 951 | 952 | $xmlvar = new \SoapVar($CAML, XSD_ANYXML); 953 | $result = NULL; 954 | 955 | // Attempt to run operation 956 | try { 957 | $result = $this->xmlHandler($this->soapClient->UpdateListItems($xmlvar)->UpdateListItemsResult->any); 958 | } catch (\SoapFault $fault) { 959 | $this->onError($fault); 960 | } 961 | 962 | // Return a XML as nice clean Array 963 | return $result; 964 | } 965 | 966 | /** 967 | * prepBatch 968 | * Convert an array of new items or change sets in to XML commands to be run on 969 | * the sharepoint SOAP API. 970 | * 971 | * @param $items array of new items/change sets 972 | * @param $method New/Update/Delete 973 | * @return XML 974 | */ 975 | public function prepBatch (array $items, $method) { 976 | // Check if method is supported 977 | assert(in_array($method, array('New', 'Update', 'Delete'))); 978 | 979 | // Get var's needed 980 | $batch = ''; 981 | $counter = 1; 982 | 983 | // Foreach item to be converted in to a SharePoint Soap Command 984 | foreach ($items as $data) { 985 | // Wrap item in command for given method 986 | $batch .= ''; 987 | 988 | // Add required attributes 989 | foreach ($data as $itm => $val) { 990 | // Add entry 991 | $batch .= '' . htmlspecialchars($val) . '' . PHP_EOL; 992 | } 993 | 994 | $batch .= ''; 995 | 996 | // Inc counter 997 | $counter++; 998 | } 999 | 1000 | // Return XML data. 1001 | return $batch; 1002 | } 1003 | 1004 | /** 1005 | * onError 1006 | * This is called when sharepoint throws an error and displays basic debug info. 1007 | * 1008 | * @param $fault Error Information 1009 | * @throws \Exception Puts data from $fault into an other exception 1010 | */ 1011 | private function onError (\SoapFault $fault) { 1012 | $more = ''; 1013 | if (isset($fault->detail->errorstring)) { 1014 | $more = 'Detailed: ' . $fault->detail->errorstring; 1015 | } 1016 | throw new \Exception('Error (' . $fault->faultcode . ') ' . $fault->faultstring . ',more=' . $more); 1017 | } 1018 | 1019 | /** 1020 | * magicLookup: Helper method 1021 | * 1022 | * If you know the name of the item you wish to link to in the lookup field, this helper method 1023 | * can be used to perform the lookup for you. 1024 | * 1025 | * @param $sp Active/current instance of sharepointAPI 1026 | * @param $name Name of item you wish lookup to reference 1027 | * @param $list Name of list item lookup is linked to. 1028 | * 1029 | * @return "lookup" value sharepoint will accept 1030 | */ 1031 | public function magicLookup ($name, $list) { 1032 | //Perform lookup for specified item on specified list 1033 | $find = $this->read($list, null, array('Title' => $name)); 1034 | //If we get a result (and there is only one of them) return it in "Lookup" format 1035 | if (isset($find[0]) && count($find) === 1) { 1036 | settype($find[0], 'array');//Set type to array in case API is in object mode. 1037 | if ($this->lower_case_indexs) { 1038 | return static::lookup($find[0]['id'], $find[0]['title']); 1039 | } else { 1040 | return static::lookup($find[0]['ID'], $find[0]['Title']); 1041 | } 1042 | } else { 1043 | //If we didnt find anything / got to many, throw exception 1044 | throw new \Exception('Unable to perform automated lookup for value in ' . $list . '.'); 1045 | } 1046 | } 1047 | 1048 | /** 1049 | * dateTime: Helper method 1050 | * Format date for use by sharepoint 1051 | * @param $date (Date to be handled by strtotime) 1052 | * @param $timestamp. If first parameter is a unix timestamp, set this to true 1053 | * 1054 | *@return date SharePoint will accept 1055 | */ 1056 | public static function dateTime ($date, $timestamp = FALSE) { 1057 | return ($timestamp) ? date('c',$date) : date('c', strtotime($date)); 1058 | } 1059 | 1060 | /** 1061 | * lookup: Helper method 1062 | * Format data to be used in lookup datatype 1063 | * @param $id ID of item in other table 1064 | * @param $title Title of item in other table (this is optional as sharepoint doesn't complain if its not provided) 1065 | * @return string "lookup" value sharepoint will accept 1066 | */ 1067 | public static function lookup ($id, $title = '') { 1068 | return $id . (($title !== '') ? ';#' . $title : ''); 1069 | } 1070 | 1071 | /** 1072 | * getFieldVersions 1073 | * Get previous versions of field contents 1074 | * 1075 | * @see https://github.com/thybag/PHP-SharePoint-Lists-API/issues/6#issuecomment-13793688 by TimRainey 1076 | * @param $list Name or GUID of list 1077 | * @param $id ID of item to find versions for 1078 | * @param $field name of column to get versions for 1079 | * @return array | object 1080 | */ 1081 | public function getFieldVersions ($list, $id, $field) { 1082 | //Ready XML 1083 | $CAML = ' 1084 | 1085 | '.$list.' 1086 | '.$id.' 1087 | '.$field.' 1088 | 1089 | '; 1090 | 1091 | // Attempt to query SharePoint 1092 | try{ 1093 | $rawxml = $this->soapClient->GetVersionCollection(new \SoapVar($CAML, XSD_ANYXML))->GetVersionCollectionResult->any; 1094 | }catch(\SoapFault $fault){ 1095 | $this->onError($fault); 1096 | } 1097 | 1098 | // Load XML in to DOM document and grab all Fields 1099 | $dom = new \DOMDocument(); 1100 | $dom->loadXML($rawxml, (LIBXML_VERSION >= 20900) ? LIBXML_PARSEHUGE : null); 1101 | $nodes = $dom->getElementsByTagName("Version"); 1102 | 1103 | // Parse results 1104 | $results = array(); 1105 | // Format data in to array or object 1106 | foreach ($nodes as $counter => $node) { 1107 | //Get Attributes 1108 | foreach ($node->attributes as $attribute => $value) { 1109 | $results[$counter][strtolower($attribute)] = $node->getAttribute($attribute); 1110 | } 1111 | //Make object if needed 1112 | if ($this->returnType === 1) settype($results[$counter], "object"); 1113 | } 1114 | // Add error array if stuff goes wrong. 1115 | if (!isset($results)) $results = array('warning' => 'No data returned.'); 1116 | 1117 | return $results; 1118 | } 1119 | public function getColumnVersions ($list, $id, $field) { return $this->getFieldVersions($list, $id, $field); } 1120 | 1121 | /** 1122 | * getVersions 1123 | * Get previous versions of a field 1124 | * 1125 | * @param $list Name or GUID of list 1126 | * @param $id ID of item to find versions for 1127 | * @param $field optional name of column to get versions for 1128 | * @return array | object 1129 | */ 1130 | public function getVersions ($list, $id, $field = null) { 1131 | return $this->getFieldVersions($list, $id, $field); 1132 | } 1133 | } --------------------------------------------------------------------------------