opswaypipeliner - Version 1.0.0

Version Notes

First release.

Download this release

Release Info

Developer OpsWay
Extension opswaypipeliner
Version 1.0.0
Comparing to
See all releases


Version 1.0.0

Files changed (32) hide show
  1. app/code/community/OpsWay/Pipeliner/Block/Adminhtml/Info.php +33 -0
  2. app/code/community/OpsWay/Pipeliner/Helper/Data.php +34 -0
  3. app/code/community/OpsWay/Pipeliner/Model/Observer.php +319 -0
  4. app/code/community/OpsWay/Pipeliner/etc/adminhtml.xml +22 -0
  5. app/code/community/OpsWay/Pipeliner/etc/config.xml +136 -0
  6. app/code/community/OpsWay/Pipeliner/etc/system.xml +68 -0
  7. app/code/community/OpsWay/Pipeliner/sql/add_category_attribute/mysql4-install-0.1.0.php +15 -0
  8. app/code/community/OpsWay/Pipeliner/sql/add_product_attribute/mysql4-install-0.1.0.php +26 -0
  9. app/etc/modules/Opsway_Pipeliner.xml +10 -0
  10. lib/PipelinerSales/Defaults.php +13 -0
  11. lib/PipelinerSales/Entity.php +225 -0
  12. lib/PipelinerSales/EntityCollection.php +156 -0
  13. lib/PipelinerSales/EntityCollectionIterator.php +111 -0
  14. lib/PipelinerSales/Http/CreatedResponse.php +20 -0
  15. lib/PipelinerSales/Http/CurlHttpClient.php +96 -0
  16. lib/PipelinerSales/Http/HttpInterface.php +34 -0
  17. lib/PipelinerSales/Http/PipelinerHttpException.php +72 -0
  18. lib/PipelinerSales/Http/Response.php +78 -0
  19. lib/PipelinerSales/InfoMethodsInterface.php +61 -0
  20. lib/PipelinerSales/Model/Version.php +101 -0
  21. lib/PipelinerSales/PipelinerClient.php +189 -0
  22. lib/PipelinerSales/PipelinerClientException.php +12 -0
  23. lib/PipelinerSales/Query/Criteria.php +308 -0
  24. lib/PipelinerSales/Query/Filter.php +132 -0
  25. lib/PipelinerSales/Query/Sort.php +78 -0
  26. lib/PipelinerSales/Repository/RepositoryFactoryInterface.php +17 -0
  27. lib/PipelinerSales/Repository/RepositoryInterface.php +140 -0
  28. lib/PipelinerSales/Repository/Rest/RestCreatedResponse.php +34 -0
  29. lib/PipelinerSales/Repository/Rest/RestInfoMethods.php +66 -0
  30. lib/PipelinerSales/Repository/Rest/RestRepository.php +229 -0
  31. lib/PipelinerSales/Repository/Rest/RestRepositoryFactory.php +32 -0
  32. package.xml +51 -0
app/code/community/OpsWay/Pipeliner/Block/Adminhtml/Info.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class OpsWay_Pipeliner_Block_Adminhtml_Info extends Mage_Adminhtml_Block_System_Config_Form_Fieldset {
3
+
4
+ public function render(Varien_Data_Form_Element_Abstract $element) {
5
+ if ($element->getHtmlId() === 'OpsWay_Pipeliner_general') {
6
+ echo $this->_getInfo();
7
+ }
8
+ return parent::render($element);
9
+
10
+ }
11
+
12
+ protected function _getInfo() {
13
+ $output = $this->_getStyle();
14
+ $output .= '<div class="opsway-info">';
15
+ $output .= '<p style="clear:both;">';
16
+ $output .= '<h2>Contact <a style="color:#eb5e00;" href="mailto:support@opsway.com">OpsWay Support team</a> or visit <a style="color:#eb5e00;" href="http://opsway.com">opsway.com</a> for additional information</h2>';
17
+ $output .= '</p>';
18
+ $output .= '</div>';
19
+ return $output;
20
+ }
21
+
22
+ protected function _getStyle() {
23
+ $content = '<style>';
24
+ $content .= '.opsway-info { border: 1px solid #cccccc; background: #e7efef; margin-bottom: 10px; padding: 10px; height: auto; }';
25
+ $content .= '.opsway-info .opsway-logo { float: right; padding: 5px; }';
26
+ $content .= '.opsway-info .opsway-command { border: 1px solid #cccccc; background: #ffffff; padding: 15px; text-align: left; margin: 10px 0; font-weight: bold; }';
27
+ $content .= '.opsway-info h3 { color: #ea7601; }';
28
+ $content .= '.opsway-info h3 small { font-weight: normal; font-size: 80%; font-style: italic; }';
29
+ $content .= '</style>';
30
+ return $content;
31
+ }
32
+
33
+ }
app/code/community/OpsWay/Pipeliner/Helper/Data.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class OpsWay_Pipeliner_Helper_Data extends Mage_Core_Helper_Abstract
3
+ {
4
+
5
+ public function getUrl()
6
+ {
7
+ return Mage::getStoreConfig('OpsWay_Pipeliner/general/service_url');
8
+ }
9
+
10
+ public function getPipelineId()
11
+ {
12
+ return Mage::getStoreConfig('OpsWay_Pipeliner/general/team_id');
13
+ }
14
+
15
+ public function getToken()
16
+ {
17
+ return Mage::getStoreConfig('OpsWay_Pipeliner/general/token');
18
+ }
19
+
20
+ public function getPassword()
21
+ {
22
+ return Mage::getStoreConfig('OpsWay_Pipeliner/general/password');
23
+ }
24
+
25
+ public function getConnection()
26
+ {
27
+ return PipelinerSales_PipelinerClient::create(
28
+ $this->getUrl(),
29
+ $this->getPipelineId(),
30
+ $this->getToken(),
31
+ $this->getPassword()
32
+ );
33
+ }
34
+ }
app/code/community/OpsWay/Pipeliner/Model/Observer.php ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class OpsWay_Pipeliner_Model_Observer
4
+ {
5
+ const PIPELINER_ROLLOUT_STAGE = 'PY-7FFFFFFF-363E1EB8-B973-46AF-BBFD-DD3CDF3C4BBC';
6
+ const COMPANY_SALES_UNIT = '0';
7
+
8
+ /**
9
+ *
10
+ * @throws Exception
11
+ * @param Varien_Event_Observer $observer
12
+ * @return $this
13
+ */
14
+ public function saveCatalogProductBefore(Varien_Event_Observer $observer)
15
+ {
16
+ try {
17
+ $_product = $observer->getProduct();
18
+
19
+ $cats = $_product->getCategoryIds();
20
+ $categoryArray = array();
21
+ $_cat = Mage::getModel('catalog/category')->load($cats[count($cats) - 1]);
22
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
23
+
24
+ $products = $pipeliner->products->get(PipelinerSales_Query_Filter::equals('SKU', $_product->getSku()));
25
+ $accountIterator = $pipeliner->products->getEntireRangeIterator($products);
26
+ foreach ($accountIterator as $account) {
27
+ $sku = $account->getId();
28
+ }
29
+ if (!$sku) {
30
+ $products = $pipeliner->products->create();
31
+ } else {
32
+ $products = $pipeliner->products->getById($sku);
33
+ }
34
+ $observer->getProduct()->setData('pipeliner_api_id', $products->getId());
35
+ $products->setDescription($_product->getDescription());
36
+ $products->setName($_product->getName());
37
+ $products->setSku($_product->getSku());
38
+ $products->setProductCategoryId($_cat->getData('pipeliner_api_id'));
39
+ $products->setUnitSymbol('ps');
40
+ $pipeliner->products->save($products);
41
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
42
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
43
+ }
44
+
45
+ }
46
+
47
+ /**
48
+ *
49
+ * @throws Exception
50
+ * @param Varien_Event_Observer $observer
51
+ * @return $this
52
+ */
53
+ public function saveCatalogCategoryBefore(Varien_Event_Observer $observer)
54
+ {
55
+ try {
56
+ $_category = $observer->getCategory();
57
+ $_cat = Mage::getModel('catalog/category')->load($_category->getId());
58
+ $categoryString = $_cat->getName();
59
+
60
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
61
+
62
+ $filter = new PipelinerSales_Query_Filter();
63
+ $filter->equals('NAME', $categoryString);
64
+ if ($_cat->getParentCategory()->getId() != 2) {
65
+ $filter->equals('PARENT_ID', $_cat->getParentCategory()->getData('pipeliner_api_id'));
66
+ }
67
+
68
+ $categories = $pipeliner->productCategories->get($filter);
69
+ $accountIterator = $pipeliner->productCategories->getEntireRangeIterator($categories);
70
+ foreach ($accountIterator as $account) {
71
+ $categoryId = $account->getId();
72
+ }
73
+
74
+ if (empty($categoryId)) {
75
+ $categories = $pipeliner->productCategories->create();
76
+ } else {
77
+ $categories = $pipeliner->productCategories->getById($categoryId);
78
+ }
79
+
80
+ $observer->getCategory()->setData('pipeliner_api_id', $categories->getId());
81
+
82
+ $categories->setName($categoryString);
83
+ if ($_cat->getParentCategory()->getId() != 2)
84
+ $categories->setParentId($_cat->getParentCategory()->getData('pipeliner_api_id'));
85
+ $pipeliner->productCategories->save($categories);
86
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
87
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Save Attribute Set Identifier as changed resource
93
+ *
94
+ * @throws Exception
95
+ * @param Varien_Event_Observer $observer
96
+ * @return $this
97
+ */
98
+ public function saveCustomerAfterCommit(Varien_Event_Observer $observer)
99
+ {
100
+ try {
101
+ $_customer = $observer->getCustomer();
102
+
103
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
104
+ $contacts = $pipeliner->contacts->get(PipelinerSales_Query_Filter::equals('EMAIL1', $_customer->getEmail()));
105
+ $accountIterator = $pipeliner->contacts->getEntireRangeIterator($contacts);
106
+ foreach ($accountIterator as $account) {
107
+ $id = $account->getId();
108
+ }
109
+ if (!$id) {
110
+ $contacts = $pipeliner->contacts->create();
111
+ } else {
112
+ $contacts = $pipeliner->contacts->getById($id);
113
+ }
114
+
115
+ $contacts->setEmail1($_customer->getEmail());
116
+ $contacts->setFirstName($_customer->getFirstname());
117
+ $contacts->setSurname($_customer->getLastname());
118
+ $contacts->setGender($_customer->getGender());
119
+ $contacts->setMiddleName($_customer->getMiddlename());
120
+ $contacts->setOwnerId($this->getOwnerId($pipeliner));
121
+ $contacts->setSalesUnitId(self::COMPANY_SALES_UNIT);
122
+ $pipeliner->contacts->save($contacts);
123
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
124
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
125
+ }
126
+
127
+ }
128
+
129
+ /**
130
+ * Save Attribute Set Identifier as changed resource
131
+ *
132
+ * @throws Exception
133
+ * @param Varien_Event_Observer $observer
134
+ * @return $this
135
+ */
136
+ public function saveOrderAfterCommit(Varien_Event_Observer $observer)
137
+ {
138
+
139
+ $_order = $observer->getOrder();
140
+
141
+ try {
142
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
143
+
144
+ $opportunities = $pipeliner->opportunities->create();
145
+
146
+ $accounts = $pipeliner->accounts->get();
147
+ $accountIterator = $pipeliner->accounts->getEntireRangeIterator($accounts);
148
+ foreach ($accountIterator as $account) {
149
+ $id = $account->getId();
150
+ break;
151
+ }
152
+
153
+ $opportunities->setAccountRelations(array(array("ACCOUNT_ID" => $id, "IS_PRIMARY" => 1)));
154
+ $opportunities->setClosingDate(date('Y-m-d H:i:s', time() + 3600*24*365));
155
+
156
+ $contacts = $pipeliner->contacts->get(PipelinerSales_Query_Filter::equals('EMAIL1', $_order->getCustomerEmail()));
157
+ $accountIterator = $pipeliner->contacts->getEntireRangeIterator($contacts);
158
+ foreach ($accountIterator as $account) {
159
+ $contactId = $account->getId();
160
+ }
161
+
162
+ $opportunities->setContactRelations(array(array("CONTACT_ID" => $contactId, "IS_PRIMARY" => 1)));
163
+ $opportunities->setOpportunityName('Purchase number #'.$_order->getIncrementId());
164
+ $opportunities->setOpportunityValue(number_format($_order->getGrandTotal(), 2, '.', ''));
165
+ $opportunities->setOpportunityValueForegin(number_format($_order->getGrandTotal(), 2, '.', ''));
166
+ $opportunities->setOwnerId($this->getOwnerId($pipeliner));
167
+ $opportunities->setQuickContactName($_order->getCustomerLastname().' '.$_order->getCustomerFirstname());
168
+ $opportunities->setQuickEmail($_order->getCustomerEmail());
169
+ $opportunities->setQuickPhone($_order->getCustomerPhone());
170
+ $opportunities->setSalesUnitId(self::COMPANY_SALES_UNIT);
171
+ $opportunities->setStage(self::PIPELINER_ROLLOUT_STAGE);
172
+ $pipeliner->opportunities->save($opportunities);
173
+
174
+
175
+ $opportunities = $pipeliner->opportunities->get(PipelinerSales_Query_Filter::equals('OPPORTUNITY_NAME', 'Purchase number #'.$_order->getIncrementId()));
176
+ $accountIterator = $pipeliner->opportunities->getEntireRangeIterator($opportunities);
177
+ foreach ($accountIterator as $account) {
178
+ $opportunityId = $account->getId();
179
+ }
180
+
181
+ foreach ($_order->getAllVisibleItems() as $item) {
182
+ $opptyProductRelations = $pipeliner->opptyProductRelations->create();
183
+
184
+ $products = $pipeliner->products->get(PipelinerSales_Query_Filter::equals('SKU', $item->getSku()));
185
+ $accountIterator = $pipeliner->products->getEntireRangeIterator($products);
186
+ foreach ($accountIterator as $account) {
187
+ $productId = $account->getId();
188
+ }
189
+
190
+ $opptyProductRelations->setOpptyId($opportunityId);
191
+ $opptyProductRelations->setProductId($productId);
192
+ $opptyProductRelations->setPrice($item->getPrice());
193
+ $opptyProductRelations->setQuantity($item->getSimpleQtyToShip());
194
+ $pipeliner->opptyProductRelations->save($opptyProductRelations);
195
+ }
196
+
197
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
198
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
199
+ }
200
+ }
201
+
202
+ public function syncCategories()
203
+ {
204
+ try {
205
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
206
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
207
+ $categories = $pipeliner->productCategories->get();
208
+ $accountIterator = $pipeliner->productCategories->getEntireRangeIterator($categories);
209
+ foreach ($accountIterator as $account) {
210
+ $category = $this->getCategoryByPipelinerId($account->getId());
211
+ if ($category) {
212
+ $categoryModel = Mage::getModel('catalog/category')->load($category->getId());
213
+ $categoryModel->setName($account->getName());
214
+ $parentId = $account->getParentId();
215
+ if (!empty($parentId)) {
216
+ $parentCategory = $this->getCategoryByPipelinerId($parentId);
217
+ $categoryModel->setPath($parentCategory->getPath());
218
+ }
219
+ $categoryModel->save();
220
+ unset($categoryModel);
221
+ } else {
222
+ $categoryModel = Mage::getModel('catalog/category');
223
+ $categoryModel->setName($account->getName());
224
+ $categoryModel->setIsActive(1);
225
+ $categoryModel->setDisplayMode('PRODUCTS');
226
+ $categoryModel->setIsAnchor(0);
227
+ $parentId = $account->getParentId();
228
+ if (!empty($parentId)) {
229
+ $parentCategory = $this->getCategoryByPipelinerId($parentId);
230
+ $categoryModel->setPath($parentCategory->getPath());
231
+ }
232
+ $categoryModel->save();
233
+ unset($categoryModel);
234
+ }
235
+ }
236
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
237
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
238
+ }
239
+ }
240
+
241
+ private function syncProducts()
242
+ {
243
+ try {
244
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
245
+ $pipeliner = Mage::helper('pipeliner')->getConnection();
246
+
247
+ $products = $pipeliner->products->get();
248
+ $accountIterator = $pipeliner->products->getEntireRangeIterator($products);
249
+ foreach ($accountIterator as $account) {
250
+ $_product = getProductByPipelinerId($account->getId());
251
+ if ($_product->getId()) {
252
+ $product = Mage::getModel('catalog/product')->load($_product->getId());
253
+ $_category = getCategoryByPipelinerId($account->getProductCategoryId());
254
+ $product->setStoreId(1)
255
+ ->setWebsiteIds(array(Mage::app()->getStore(true)->getWebsite()->getId()))
256
+ ->setSku($account->getSku())
257
+ ->setAttributeSetId(4)
258
+ ->setName($account->getName())
259
+ ->setDescription($account->getDescription())
260
+ ->setShortDescription($account->getDescription())
261
+ ->setCategoryIds(array($_category->getId()))
262
+ ->setData('pipeliner_api_id', $account->getId());
263
+ $product->save();
264
+ unset($product);
265
+ } else {
266
+ $product = Mage::getModel('catalog/product');
267
+ $_category = getCategoryByPipelinerId($account->getProductCategoryId());
268
+ $product->setStoreId(1)
269
+ ->setWebsiteIds(array(Mage::app()->getStore(true)->getWebsite()->getId()))
270
+ ->setAttributeSetId(4)
271
+ ->setTypeId('simple')
272
+ ->setCreatedAt(strtotime('now'))
273
+ ->setSku($account->getSku())
274
+ ->setName($account->getName())
275
+ ->setWeight(0.0000)
276
+ ->setStatus(1)
277
+ ->setTaxClassId(1)
278
+ ->setVisibility(Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH)
279
+ ->setDescription($account->getDescription())
280
+ ->setShortDescription($account->getDescription())
281
+ ->setCategoryIds(array($_category->getId()))
282
+ ->setData('pipeliner_api_id', $account->getId());
283
+ $product->save();
284
+ unset($product);
285
+ }
286
+ }
287
+ } catch (PipelinerSales_Http_PipelinerHttpException $e) {
288
+ Mage::log($e->getErrorMessage(), null, 'pipeliner.log');
289
+ }
290
+ }
291
+
292
+ private function getCategoryByPipelinerId($id)
293
+ {
294
+ return Mage::getModel('catalog/category')
295
+ ->getCollection()
296
+ ->addAttributeToSelect('*')
297
+ ->addAttributeToFilter('pipeliner_api_id',$id)
298
+ ->load()
299
+ ->getFirstItem();
300
+ }
301
+
302
+ private function getProductByPipelinerId($id)
303
+ {
304
+ return Mage::getModel('catalog/product')
305
+ ->getCollection()
306
+ ->addAttributeToSelect('*')
307
+ ->addAttributeToFilter('pipeliner_api_id',$id)
308
+ ->load()
309
+ ->getFirstItem();
310
+ }
311
+
312
+ private function getOwnerId($pipeliner) {
313
+ $clients = $pipeliner->clients->get();
314
+ $accountIterator = $pipeliner->clients->getEntireRangeIterator($clients);
315
+ foreach ($accountIterator as $account) {
316
+ return $account->getId();
317
+ }
318
+ }
319
+ }
app/code/community/OpsWay/Pipeliner/etc/adminhtml.xml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <acl>
4
+ <resources>
5
+ <admin>
6
+ <children>
7
+ <system>
8
+ <children>
9
+ <config>
10
+ <children>
11
+ <OpsWay_Pipeliner>
12
+ <title>Pipiliner API Settings</title> <!-- Used in resources tree -->
13
+ </OpsWay_Pipeliner>
14
+ </children>
15
+ </config>
16
+ </children>
17
+ </system>
18
+ </children>
19
+ </admin>
20
+ </resources>
21
+ </acl>
22
+ </config>
app/code/community/OpsWay/Pipeliner/etc/config.xml ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <OpsWay_Pipeliner>
5
+ <version>1.0.0</version>
6
+ <depends>
7
+ <!-- no dependencies -->
8
+ </depends>
9
+ </OpsWay_Pipeliner>
10
+ </modules>
11
+
12
+ <global>
13
+ <models>
14
+ <pipeliner>
15
+ <class>OpsWay_Pipeliner_Model</class>
16
+ </pipeliner>
17
+ </models>
18
+
19
+ <helpers>
20
+ <pipeliner>
21
+ <class>OpsWay_Pipeliner_Helper</class>
22
+ </pipeliner>
23
+ </helpers>
24
+
25
+ <blocks>
26
+ <pipeliner>
27
+ <class>OpsWay_Pipeliner_Block</class>
28
+ </pipeliner>
29
+ <adminhtml>
30
+ <rewrite>
31
+ <system_config_form_fieldset>OpsWay_Pipeliner_Block_Adminhtml_Info</system_config_form_fieldset>
32
+ </rewrite>
33
+ </adminhtml>
34
+ </blocks>
35
+
36
+ <resources>
37
+ <add_category_attribute>
38
+ <setup>
39
+ <module>OpsWay_Pipeliner</module>
40
+ <class>Mage_Catalog_Model_Resource_Setup</class>
41
+ </setup>
42
+ <connection>
43
+ <use>core_setup</use>
44
+ </connection>
45
+ </add_category_attribute>
46
+ <add_category_attribute_write>
47
+ <connection>
48
+ <use>core_write</use>
49
+ </connection>
50
+ </add_category_attribute_write>
51
+ <add_category_attribute_read>
52
+ <connection>
53
+ <use>core_read</use>
54
+ </connection>
55
+ </add_category_attribute_read>
56
+ <add_product_attribute>
57
+ <setup>
58
+ <module>OpsWay_Pipeliner</module>
59
+ <class>Mage_Catalog_Model_Resource_Setup</class>
60
+ </setup>
61
+ <connection>
62
+ <use>core_setup</use>
63
+ </connection>
64
+ </add_product_attribute>
65
+ <add_product_attribute_write>
66
+ <connection>
67
+ <use>core_write</use>
68
+ </connection>
69
+ </add_product_attribute_write>
70
+ <add_product_attribute_read>
71
+ <connection>
72
+ <use>core_read</use>
73
+ </connection>
74
+ </add_product_attribute_read>
75
+ </resources>
76
+ <events>
77
+ <catalog_category_save_before>
78
+ <observers>
79
+ <pipeliner_events>
80
+ <class>OpsWay_Pipeliner_Model_Observer</class>
81
+ <method>saveCatalogCategoryBefore</method>
82
+ </pipeliner_events>
83
+ </observers>
84
+ </catalog_category_save_before>
85
+ <catalog_product_save_before>>
86
+ <observers>
87
+ <pipeliner_events>
88
+ <class>OpsWay_Pipeliner_Model_Observer</class>
89
+ <method>saveCatalogProductBefore</method>
90
+ </pipeliner_events>
91
+ </observers>
92
+ </catalog_product_save_before>>
93
+ <customer_save_commit_after>
94
+ <observers>
95
+ <pipeliner_events>
96
+ <class>OpsWay_Pipeliner_Model_Observer</class>
97
+ <method>saveCustomerAfterCommit</method>
98
+ </pipeliner_events>
99
+ </observers>
100
+ </customer_save_commit_after>
101
+ <sales_order_save_after>
102
+ <observers>
103
+ <pipeliner_events>
104
+ <class>OpsWay_Pipeliner_Model_Observer</class>
105
+ <method>saveOrderAfterCommit</method>
106
+ </pipeliner_events>
107
+ </observers>
108
+ </sales_order_save_after>
109
+ </events>
110
+ </global>
111
+ <crontab>
112
+ <jobs>
113
+ <pipeliner>
114
+ <schedule><cron_expr>*/10 * * * *</cron_expr></schedule>
115
+ <run><model>pipeliner/observer::syncCategories</model></run>
116
+ </pipeliner>
117
+ </jobs>
118
+ <jobs>
119
+ <pipeliner>
120
+ <schedule><cron_expr>*/10 * * * *</cron_expr></schedule>
121
+ <run><model>pipeliner/observer::syncProducts</model></run>
122
+ </pipeliner>
123
+ </jobs>
124
+ </crontab>
125
+ <frontend>
126
+ <routers>
127
+ <pipeliner>
128
+ <use>standard</use>
129
+ <args>
130
+ <module>OpsWay_Pipeliner</module>
131
+ <frontName>pipeliner</frontName>
132
+ </args>
133
+ </pipeliner>
134
+ </routers>
135
+ </frontend>
136
+ </config>
app/code/community/OpsWay/Pipeliner/etc/system.xml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <tabs>
4
+ <opsway translate="label">
5
+ <label>OpsWay</label>
6
+ <sort_order>150</sort_order>
7
+ </opsway>
8
+ </tabs>
9
+ <sections>
10
+ <OpsWay_Pipeliner translate="label" module="pipeliner">
11
+ <label>Pipeliner API Access</label>
12
+ <tab>opsway</tab>
13
+ <sort_order>10</sort_order>
14
+ <show_in_default>1</show_in_default>
15
+ <show_in_website>1</show_in_website>
16
+ <show_in_store>1</show_in_store>
17
+ <groups>
18
+ <general translate="label comment">
19
+ <label>General Settings</label>
20
+ <sort_order>50</sort_order>
21
+ <show_in_default>1</show_in_default>
22
+ <show_in_website>1</show_in_website>
23
+ <show_in_store>1</show_in_store>
24
+ <fields>
25
+
26
+ <token translate="label comment">
27
+ <label>Pipeliner API Token</label>
28
+ <frontend_type>text</frontend_type>
29
+ <sort_order>10</sort_order>
30
+ <show_in_default>1</show_in_default>
31
+ <show_in_website>0</show_in_website>
32
+ <show_in_store>0</show_in_store>
33
+ </token>
34
+
35
+ <password translate="label comment">
36
+ <label>Pipeliner API Password</label>
37
+ <frontend_type>text</frontend_type>
38
+ <sort_order>10</sort_order>
39
+ <show_in_default>1</show_in_default>
40
+ <show_in_website>0</show_in_website>
41
+ <show_in_store>0</show_in_store>
42
+ </password>
43
+
44
+ <team_id translate="label comment">
45
+ <label>Pipeliner Team ID</label>
46
+ <frontend_type>text</frontend_type>
47
+ <sort_order>10</sort_order>
48
+ <show_in_default>1</show_in_default>
49
+ <show_in_website>0</show_in_website>
50
+ <show_in_store>0</show_in_store>
51
+ </team_id>
52
+
53
+ <service_url translate="label comment">
54
+ <label>Pipeliner Service URL</label>
55
+ <frontend_type>text</frontend_type>
56
+ <sort_order>10</sort_order>
57
+ <show_in_default>1</show_in_default>
58
+ <show_in_website>0</show_in_website>
59
+ <show_in_store>0</show_in_store>
60
+ </service_url>
61
+
62
+
63
+ </fields>
64
+ </general>
65
+ </groups>
66
+ </OpsWay_Pipeliner>
67
+ </sections>
68
+ </config>
app/code/community/OpsWay/Pipeliner/sql/add_category_attribute/mysql4-install-0.1.0.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $this->startSetup();
3
+ $this->addAttribute(Mage_Catalog_Model_Category::ENTITY, 'pipeliner_api_id', array(
4
+ 'group' => 'General Information',
5
+ 'input' => 'text',
6
+ 'type' => 'text',
7
+ 'label' => 'Pipeliner ID',
8
+ 'backend' => '',
9
+ 'visible' => true,
10
+ 'required' => false,
11
+ 'visible_on_front' => false,
12
+ 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
13
+ ));
14
+
15
+ $this->endSetup();
app/code/community/OpsWay/Pipeliner/sql/add_product_attribute/mysql4-install-0.1.0.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $this->startSetup();
3
+ $objCatalogEavSetup = Mage::getResourceModel('catalog/eav_mysql4_setup', 'core_setup');
4
+ $objCatalogEavSetup->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'pipeliner_api_id', array(
5
+ 'group' => 'General',
6
+ 'sort_order' => 100,
7
+ 'type' => 'text',
8
+ 'backend' => '',
9
+ 'frontend' => '',
10
+ 'label' => 'Pipeliner ID',
11
+ 'note' => 'Pipeliner ID',
12
+ 'input' => 'text',
13
+ 'class' => '',
14
+ 'source' => '',
15
+ 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
16
+ 'visible' => true,
17
+ 'required' => false,
18
+ 'user_defined' => true,
19
+ 'default' => '',
20
+ 'visible_on_front' => false,
21
+ 'unique' => false,
22
+ 'is_configurable' => false,
23
+ 'used_for_promo_rules' => false
24
+ ));
25
+
26
+ $this->endSetup();
app/etc/modules/Opsway_Pipeliner.xml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <OpsWay_Pipeliner>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ <depends></depends>
8
+ </OpsWay_Pipeliner>
9
+ </modules>
10
+ </config>
lib/PipelinerSales/Defaults.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ class PipelinerSales_Defaults
10
+ {
11
+ const DATE_FORMAT = 'Y-m-d H:i:s';
12
+ const DEFAULT_LIMIT = 25;
13
+ }
lib/PipelinerSales/Entity.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Represents an entity.
11
+ *
12
+ * Aside from being able to set entity fields via the {@see setField} method, it also
13
+ * has automatic getters/setters in camel case and supports array access. These three
14
+ * ways of setting a field are therefore equivalent:
15
+ * <code>
16
+ * $entity->setOwnerId(1);
17
+ * $entity->setField('OWNER_ID', 1);
18
+ * $entity['OWNER_ID'] = 1;
19
+ * </code>
20
+ *
21
+ * For standard fields, the magic getters/setters are considered the preferred method.
22
+ *
23
+ * For a full list of supported entities and their fields, see the
24
+ * {@link http://workspace.pipelinersales.com/community/api/data/Entities.html API documentation}.
25
+ */
26
+ class PipelinerSales_Entity implements \ArrayAccess
27
+ {
28
+
29
+ private $type;
30
+ private $values = array();
31
+ private $modified = array();
32
+ private $dateTimeFormat;
33
+
34
+ /**
35
+ * @param string $type type of the entity, e.g. Account, see
36
+ * {@link http://workspace.pipelinersales.com/community/api/data/Entities.html
37
+ * list of entities}
38
+ * @param string $dateTimeFormat the format for converting DateTime objects into strings
39
+ */
40
+ public function __construct($type, $dateTimeFormat)
41
+ {
42
+ $this->type = $type;
43
+ $this->dateTimeFormat = $dateTimeFormat;
44
+ }
45
+
46
+ public function __call($name, $arguments)
47
+ {
48
+ $prefix = substr($name, 0, 3);
49
+ if ($prefix == 'get' or $prefix == 'set') {
50
+ $fieldName = $this->nameFromCamelCase(substr($name, 3));
51
+
52
+ if ($prefix == 'set') {
53
+ return $this->setField($fieldName, $arguments[0]);
54
+ } else {
55
+ return $this->getField($fieldName);
56
+ }
57
+ }
58
+ throw new \BadMethodCallException('Call to a non-existent method \'' . $name . '\'');
59
+ }
60
+
61
+ /**
62
+ * Sets this entity's field and adds it to the list of modified fields.
63
+ * Returns $this, which allows for chaining of setters.
64
+ *
65
+ * @param string $fieldName Name of the field to set, see
66
+ * {@link http://workspace.pipelinersales.com/community/api/data/Entities.html API documentation}.
67
+ * All standard fields are in upper-case, with underscore between the words (e.g. FORM_TYPE).
68
+ * Custom fields don't have this requirement.
69
+ * @param mixed $value Value to set the fields to. DateTime objects are automatically converted
70
+ * to strings according to the configured format (so calling a getter afterwards will only
71
+ * return this string). Other types of values are used directly.
72
+ * @return Entity
73
+ */
74
+ public function setField($fieldName, $value)
75
+ {
76
+ if ($value instanceof \DateTime) {
77
+ $value = $this->convertDateTime($value);
78
+ }
79
+
80
+ $this->values[$fieldName] = $value;
81
+ $this->modified[$fieldName] = true;
82
+ return $this;
83
+ }
84
+
85
+ /**
86
+ * Sets values of multiple fields at once. Fields not present in the array
87
+ * will not be changed in any way.
88
+ * @param array $values an associative array of fields to values
89
+ */
90
+ public function setFields($values)
91
+ {
92
+ foreach ($values as $field => $value) {
93
+ $this->setField($field, $value);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Unsets the specified field. This means that the field will not be
99
+ * changed upon saving. This affects both the "full update" and the
100
+ * "modified only" update.
101
+ * @return Entity
102
+ */
103
+ public function unsetField($fieldName)
104
+ {
105
+ unset($this->values[$fieldName]);
106
+ unset($this->modified[$fieldName]);
107
+ return $this;
108
+ }
109
+
110
+ /**
111
+ * Returns the value of the specified field in this entity. A PHP notice will be raised
112
+ * if the value hasn't been set yet.
113
+ * @param string $fieldName Name of the field, see the
114
+ * {@link http://workspace.pipelinersales.com/community/api/data/Entities.html API documentation}.
115
+ * @return mixed
116
+ */
117
+ public function getField($fieldName)
118
+ {
119
+ return $this->values[$fieldName];
120
+ }
121
+
122
+ /**
123
+ * Returns an associative array of all fields and their values
124
+ * @return array
125
+ */
126
+ public function getFields()
127
+ {
128
+ return $this->values;
129
+ }
130
+
131
+ /**
132
+ * Returns an associative array of fields which were modified since the entity was created/loaded,
133
+ * and their values
134
+ * @return string[]
135
+ */
136
+ public function getModifiedFields()
137
+ {
138
+ return array_intersect_key($this->values, $this->modified);
139
+ }
140
+
141
+ /**
142
+ * Returns true if this field has some configured value.
143
+ * @param string $fieldName
144
+ * @return boolean
145
+ */
146
+ public function isFieldSet($fieldName)
147
+ {
148
+ return isset($this->values[$fieldName]);
149
+ }
150
+
151
+ private function convertDateTime(\DateTime $dateTime)
152
+ {
153
+ $dateTimeCopy = clone $dateTime;
154
+ $dateTimeCopy->setTimezone(new \DateTimeZone('UTC'));
155
+ return $dateTimeCopy->format($this->dateTimeFormat);
156
+ }
157
+
158
+ /**
159
+ * Converts a NameLikeThis (used in getters/setters) into a NAME_LIKE_THIS (used in fields)
160
+ */
161
+ private function nameFromCamelCase($name)
162
+ {
163
+ return strtoupper(substr(preg_replace('/([A-Z])/', '_\1', $name), 1));
164
+ }
165
+
166
+ /**
167
+ * Returns a JSON-encoded string representing this entity. It will contain all
168
+ * of this entity's fields.
169
+ *
170
+ * @return string
171
+ */
172
+ public function allToJson()
173
+ {
174
+ return json_encode($this->values);
175
+ }
176
+
177
+ /**
178
+ * Returns a JSON-encoded string representing this entity, which will only contain
179
+ * fields that have been modified since the entity was last loaded or saved.
180
+ *
181
+ * @return string
182
+ */
183
+ public function modifiedToJson()
184
+ {
185
+ return json_encode(array_intersect_key($this->values, $this->modified));
186
+ }
187
+
188
+ /**
189
+ * Resets the list of modified fields. All fields will be considered "not modified".
190
+ * {@see RestRepository} calls this automatically after successfully saving on the server.
191
+ */
192
+ public function resetModified()
193
+ {
194
+ $this->modified = array();
195
+ }
196
+
197
+ /**
198
+ * Returns the type of this entity.
199
+ * @return string
200
+ */
201
+ public function getType()
202
+ {
203
+ return $this->type;
204
+ }
205
+
206
+ public function offsetExists($offset)
207
+ {
208
+ return $this->isFieldSet($offset);
209
+ }
210
+
211
+ public function offsetGet($offset)
212
+ {
213
+ return $this->getField($offset);
214
+ }
215
+
216
+ public function offsetSet($offset, $value)
217
+ {
218
+ $this->setField($offset, $value);
219
+ }
220
+
221
+ public function offsetUnset($offset)
222
+ {
223
+ $this->unsetField($offset);
224
+ }
225
+ }
lib/PipelinerSales/EntityCollection.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Represents an immutable collection of multiple entities of a common type. The
11
+ * entities themselves are mutable (i.e. you can modify the entity object inside
12
+ * the collection, but you can not add/remove/reorder/etc. entities from/to the collection.
13
+ *
14
+ * Works like a regular PHP array, but contains additional information about the
15
+ * number of entities that were not loaded due to a query limit.
16
+ *
17
+ * @method Entity[] getArrayCopy
18
+ */
19
+ class PipelinerSales_EntityCollection extends \ArrayObject
20
+ {
21
+
22
+ private $criteria;
23
+ private $totalCount;
24
+ private $startIndex;
25
+ private $endIndex;
26
+ private static $immutableError = 'EntityCollection is immutable';
27
+
28
+ /**
29
+ * Creates a collection based on an existing array
30
+ *
31
+ * @param array $data
32
+ * @param mixed $criteria Criteria that was used when querying for this colleciton. Any type
33
+ * that the can be passed to the constructor of {@see Criteria} can be used here. A copy of this
34
+ * argument will be made, so it is safe to pass a Criteria object - it will not be inadvertently
35
+ * modified at a later time.
36
+ * @param integer $startIndex the offset used in the query
37
+ * @param integer $endIndex the offset of the last loaded entity
38
+ * @param integer $totalCount total count of all entities that match the query
39
+ * @throws PipelinerSales_PipelinerClientException when the number of items in the array doesn't match the specified indexes
40
+ */
41
+ public function __construct($data, $criteria, $startIndex, $endIndex, $totalCount)
42
+ {
43
+ parent::__construct($data);
44
+
45
+ //sanity check
46
+ if ($endIndex - $startIndex + 1 != $this->count()) {
47
+ //empty collections are a special case - Pipeliner's server currently returns 0 - -1 range in such a case
48
+ if ($endIndex != -1) {
49
+ throw new
50
+ PipelinerSales_PipelinerClientException('Returned content range doesn\'t match the number of returned entities');
51
+ }
52
+ }
53
+
54
+ $this->criteria = new PipelinerSales_Query_Criteria($criteria);
55
+ $this->startIndex = $startIndex;
56
+ $this->totalCount = $totalCount;
57
+ $this->endIndex = $endIndex;
58
+ }
59
+
60
+ /**
61
+ * Returns the number of entities available on the server (ignoring the limit).
62
+ * @return integer
63
+ */
64
+ public function getTotalCount()
65
+ {
66
+ return $this->totalCount;
67
+ }
68
+
69
+ /**
70
+ * Returns a copy of the criteria that was used to fetch this collection.
71
+ * @return Criteria
72
+ */
73
+ public function getCriteriaCopy()
74
+ {
75
+ return clone $this->criteria;
76
+ }
77
+
78
+ /**
79
+ * Returns the index of the first item in this colleciton. It is
80
+ * identical to the offset that was used to fetch this collection.
81
+ * @return integer
82
+ */
83
+ public function getStartIndex()
84
+ {
85
+ return $this->startIndex;
86
+ }
87
+
88
+ /**
89
+ * Returns the index of the last item in this collection.
90
+ * @return integer
91
+ */
92
+ public function getEndIndex()
93
+ {
94
+ return $this->endIndex;
95
+ }
96
+
97
+ /** @ignore */
98
+ public function offsetSet($index, $newval)
99
+ {
100
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
101
+ }
102
+
103
+ /** @ignore */
104
+ public function offsetUnset($index)
105
+ {
106
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
107
+ }
108
+
109
+ /** @ignore */
110
+ public function exchangeArray($input)
111
+ {
112
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
113
+ }
114
+
115
+ /** @ignore */
116
+ public function asort()
117
+ {
118
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
119
+ }
120
+
121
+ /** @ignore */
122
+ public function natsort()
123
+ {
124
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
125
+ }
126
+
127
+ /** @ignore */
128
+ public function append($value)
129
+ {
130
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
131
+ }
132
+
133
+ /** @ignore */
134
+ public function natcasesort()
135
+ {
136
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
137
+ }
138
+
139
+ /** @ignore */
140
+ public function uasort($cmp_function)
141
+ {
142
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
143
+ }
144
+
145
+ /** @ignore */
146
+ public function uksort($cmp_function)
147
+ {
148
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
149
+ }
150
+
151
+ /** @ignore */
152
+ public function ksort()
153
+ {
154
+ throw new PipelinerSales_PipelinerClientException(self::$immutableError);
155
+ }
156
+ }
lib/PipelinerSales/EntityCollectionIterator.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Class for conveniently iterating through collections of entities beyond the
11
+ * ones which have been loaded from the server.
12
+ *
13
+ * Represents a range 0-n, where n is the number of entities that the query
14
+ * would have returned if no limit was set. Only a part of this range is loaded
15
+ * at a time, so iterating may cause additional HTTP requests to be issued.
16
+ */
17
+ class PipelinerSales_EntityCollectionIterator implements \SeekableIterator
18
+ {
19
+ /** @var Query\Criteria */
20
+ private $criteria;
21
+ private $repository;
22
+ private $collection;
23
+ private $position;
24
+
25
+ /**
26
+ * @param RepositoryInterface $repository repository for loading additional entities
27
+ * @param EntityCollection $collection initial collection of already loaded entities
28
+ */
29
+ public function __construct(PipelinerSales_Repository_Rest_RestRepository $repository, PipelinerSales_EntityCollection $collection)
30
+ {
31
+ $this->repository = $repository;
32
+ $this->collection = $collection;
33
+
34
+ $this->criteria = $this->collection->getCriteriaCopy();
35
+ $this->position = $this->criteria->getOffset(true);
36
+ }
37
+
38
+ /**
39
+ * @return Entity
40
+ */
41
+ public function current()
42
+ {
43
+ if ($this->dataAvailable()) {
44
+ return $this->collection->offsetGet($this->position - $this->collection->getStartIndex());
45
+ } else {
46
+ $this->criteria->offset($this->position);
47
+ $this->collection = $this->repository->get($this->criteria);
48
+ return $this->collection->offsetGet($this->position - $this->collection->getStartIndex());
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @return integer
54
+ */
55
+ public function key()
56
+ {
57
+ return $this->position;
58
+ }
59
+
60
+ public function next()
61
+ {
62
+ $this->position++;
63
+ }
64
+
65
+ public function rewind()
66
+ {
67
+ $this->position = 0;
68
+ }
69
+
70
+ public function valid()
71
+ {
72
+ return ($this->position >= 0 and $this->position < $this->collection->getTotalCount());
73
+ }
74
+
75
+ /**
76
+ * True if data for the current position has been loaded from the server and is
77
+ * locally available.
78
+ * @return boolean
79
+ */
80
+ public function dataAvailable()
81
+ {
82
+ return ($this->position >= $this->collection->getStartIndex() and
83
+ $this->position <= $this->collection->getEndIndex());
84
+ }
85
+
86
+ /**
87
+ * True if data for the next position has been loaded from the server and is
88
+ * locally available. This is useful in foreach loops to determine whether the
89
+ * next iteration will issue a HTTP request.
90
+ * @return boolean
91
+ */
92
+ public function nextDataAvailable()
93
+ {
94
+ return ($this->position+1 >= $this->collection->getStartIndex() and
95
+ $this->position+1 <= $this->collection->getEndIndex());
96
+ }
97
+
98
+ /**
99
+ * True if the current position is the last position within the range.
100
+ * @return boolean
101
+ */
102
+ public function atEnd()
103
+ {
104
+ return ($this->position == $this->collection->getTotalCount()-1);
105
+ }
106
+
107
+ public function seek($position)
108
+ {
109
+ $this->position = $position;
110
+ }
111
+ }
lib/PipelinerSales/Http/CreatedResponse.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Represents a HTTP 201 Created response
11
+ */
12
+ abstract class PipelinerSales_Http_CreatedResponse extends PipelinerSales_Http_Response
13
+ {
14
+
15
+ /**
16
+ * Returns the ID of the newly created entity
17
+ * @return string
18
+ */
19
+ abstract public function getCreatedId();
20
+ }
lib/PipelinerSales/Http/CurlHttpClient.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Implementation of HttpInterface using cURL to issue the requests.
11
+ */
12
+ class PipelinerSales_Http_CurlHttpClient implements PipelinerSales_Http_HttpInterface
13
+ {
14
+
15
+ private $extraCurlOptions = null;
16
+ private $username;
17
+ private $password;
18
+ private $userAgent;
19
+
20
+ public function __construct($userAgent = 'Pipeliner_PHP_API_Client/1.0')
21
+ {
22
+ $this->userAgent = $userAgent;
23
+ }
24
+
25
+ public function request($method, $url, $rawPayload = null, $contentType = 'application/json')
26
+ {
27
+ $ch = curl_init($url);
28
+
29
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
30
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
31
+ curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent);
32
+ curl_setopt($ch, CURLOPT_HEADER, 1);
33
+ if (!empty($rawPayload)) {
34
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $rawPayload);
35
+ curl_setopt(
36
+ $ch,
37
+ CURLOPT_HTTPHEADER,
38
+ array(
39
+ 'Content-Length: ' . strlen($rawPayload),
40
+ 'Content-Type: ' . $contentType
41
+ )
42
+ );
43
+ }
44
+
45
+ if (!empty($this->extraCurlOptions)) {
46
+ curl_setopt_array($ch, $this->extraCurlOptions);
47
+ }
48
+
49
+ if (!empty($this->username)) {
50
+ curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);
51
+ }
52
+
53
+ $result = curl_exec($ch);
54
+
55
+ if ($result !== false) {
56
+ list($headers, $body) = explode("\r\n\r\n", $result, 2);
57
+ } else {
58
+ list($headers, $body) = array('', '');
59
+ }
60
+ $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
61
+
62
+ if ($result === false or $status < 200 or $status > 399) {
63
+ throw new PipelinerSales_Http_PipelinerHttpException(
64
+ new PipelinerSales_Http_Response($body, $headers, $status, $url, $method),
65
+ '',
66
+ curl_error($ch)
67
+ );
68
+ }
69
+
70
+ return new PipelinerSales_Http_Response($body, $headers, $status, $url, $method);
71
+ }
72
+
73
+ public function setUserCredentials($username, $password)
74
+ {
75
+ $this->username = $username;
76
+ $this->password = $password;
77
+ }
78
+
79
+ /**
80
+ * Sets the user agent header used in HTTP requests
81
+ * @param string $userAgent
82
+ */
83
+ public function setUserAgent($userAgent)
84
+ {
85
+ $this->userAgent = $userAgent;
86
+ }
87
+
88
+ /**
89
+ * Sets extra options that will be passed to curl for every request.
90
+ * @param array $extraOptions an array in the format expected by curl_setopt_array
91
+ */
92
+ public function setExtraCurlOptions(array $extraOptions)
93
+ {
94
+ $this->extraCurlOptions = $extraOptions;
95
+ }
96
+ }
lib/PipelinerSales/Http/HttpInterface.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * A basic interface for HTTP clients
11
+ */
12
+ interface PipelinerSales_Http_HttpInterface
13
+ {
14
+ /**
15
+ * Sends a HTTP request
16
+ *
17
+ * @param string $method HTTP method
18
+ * @param string $url The URL
19
+ * @param string $rawPayload Body of the request in case of POST/PUT requests. Must already
20
+ * be encoded in the form expected by the server. Pipeliner's server expects JSON.
21
+ * @param string $contentType If $rawPayload is not null, specifies the content of the Content-Type header.
22
+ * @return Response
23
+ * @throws PipelinerHttpException
24
+ */
25
+ public function request($method, $url, $rawPayload = null, $contentType = 'application/json');
26
+
27
+ /**
28
+ * Sets the user credentials used for authentication
29
+ *
30
+ * @param string $username
31
+ * @param string $password
32
+ */
33
+ public function setUserCredentials($username, $password);
34
+ }
lib/PipelinerSales/Http/PipelinerHttpException.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Exception related to HTTP requests.
11
+ */
12
+ class PipelinerSales_Http_PipelinerHttpException extends PipelinerSales_PipelinerClientException
13
+ {
14
+
15
+ /** @var Response $response */
16
+ private $response;
17
+ private $jsonError = array();
18
+
19
+ public function __construct($response, $message, $httpError = '', $code = 0, $previous = null)
20
+ {
21
+ parent::__construct($message, $code, $previous);
22
+ $this->response = $response;
23
+
24
+ if (!empty($httpError)) {
25
+ $this->message .= 'HTTP error: [' . $httpError . ']';
26
+ }
27
+
28
+ $body = $this->response->getBody();
29
+ if (!empty($body)) {
30
+ $this->jsonError = json_decode($body, true);
31
+ if (!empty($this->jsonError)) {
32
+ $this->message .= 'Response error: [' . $this->getErrorCode() . ': ' . $this->getErrorMessage() . ']';
33
+ }
34
+ }
35
+
36
+ $this->message .= ', HTTP code ' . $this->response->getStatusCode();
37
+ }
38
+
39
+ /**
40
+ * @return Response
41
+ */
42
+ public function getHttpResponse()
43
+ {
44
+ return $this->response;
45
+ }
46
+
47
+ /**
48
+ * The error code specified in the API, or 0 if the error response is not available.
49
+ * @return integer
50
+ */
51
+ public function getErrorCode()
52
+ {
53
+ if (isset($this->jsonError['errorcode'])) {
54
+ return intval($this->jsonError['errorcode']);
55
+ }
56
+ return 0;
57
+ }
58
+
59
+ /**
60
+ * The error message specified in the API, or an empty string if not available.
61
+ * @return string
62
+ */
63
+ public function getErrorMessage()
64
+ {
65
+ if (is_string($this->jsonError)) {
66
+ return $this->jsonError;
67
+ } elseif (isset($this->jsonError['message'])) {
68
+ return $this->jsonError['message'];
69
+ }
70
+ return '';
71
+ }
72
+ }
lib/PipelinerSales/Http/Response.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Represents a HTTP response.
11
+ */
12
+ class PipelinerSales_Http_Response
13
+ {
14
+
15
+ private $body;
16
+ private $headers;
17
+ private $statusCode;
18
+ private $requestMethod;
19
+ private $requestUrl;
20
+
21
+ public function __construct($body, $headers, $statusCode, $requestUrl, $requestMethod)
22
+ {
23
+ $this->body = $body;
24
+ $this->headers = $headers;
25
+ $this->statusCode = $statusCode;
26
+ $this->requestUrl = $requestUrl;
27
+ $this->requestMethod = $requestMethod;
28
+ }
29
+
30
+ /**
31
+ * @return string
32
+ */
33
+ public function getBody()
34
+ {
35
+ return $this->body;
36
+ }
37
+
38
+ /**
39
+ * Returns the raw headers of the response in a single string
40
+ * @return string
41
+ */
42
+ public function getHeaders()
43
+ {
44
+ return $this->headers;
45
+ }
46
+
47
+ public function getStatusCode()
48
+ {
49
+ return $this->statusCode;
50
+ }
51
+
52
+ public function getRequestMethod()
53
+ {
54
+ return $this->requestMethod;
55
+ }
56
+
57
+ public function getRequestUrl()
58
+ {
59
+ return $this->requestUrl;
60
+ }
61
+
62
+ /**
63
+ * Decodes the response's body into an object.
64
+ * @param boolean $assoc when true, returned objects will be converted into associative arrays.
65
+ * @return \stdClass|array
66
+ * @throws PipelinerSales_Http_PipelinerHttpException on error while decoding the json string
67
+ */
68
+ public function decodeJson($assoc = false)
69
+ {
70
+ $result = json_decode($this->body, $assoc);
71
+
72
+ if (json_last_error() !== JSON_ERROR_NONE) {
73
+ throw new PipelinerSales_Http_PipelinerHttpException($this, "Error while parsing returned JSON");
74
+ }
75
+
76
+ return $result;
77
+ }
78
+ }
lib/PipelinerSales/InfoMethodsInterface.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Methods for retrieving information describing data and configuration on the server.
11
+ *
12
+ * See the <i>Miscellaneous REST methods</i> section at
13
+ * {@link http://workspace.pipelinersales.com/community/api/data/Methods_rest.html} for details.
14
+ */
15
+ interface PipelinerSales_InfoMethodsInterface
16
+ {
17
+
18
+ /**
19
+ * @return string
20
+ */
21
+ public function fetchTeamPipelineUrl();
22
+
23
+ /**
24
+ * @return integer
25
+ */
26
+ public function fetchTeamPipelineVersion();
27
+
28
+ /**
29
+ * @return string
30
+ */
31
+ public function fetchServerAPIUtcDateTime();
32
+
33
+ /**
34
+ * Returns a list of possible error codes along with their messages
35
+ *
36
+ * @return array
37
+ */
38
+ public function fetchErrorCodes();
39
+
40
+ /**
41
+ * Returns a list of existing types of collections
42
+ *
43
+ * @return string[]
44
+ */
45
+ public function fetchCollections();
46
+
47
+ /**
48
+ * Returns a list of existing types of entities
49
+ *
50
+ * @return string[]
51
+ */
52
+ public function fetchEntityPublic();
53
+
54
+ /**
55
+ * Returns a list of fields that exist for a specified entity
56
+ *
57
+ * @param Entity|string $entity an entity object or a string with the entity's type (e.g. Account)
58
+ * @return array
59
+ */
60
+ public function fetchEntityFields($entity);
61
+ }
lib/PipelinerSales/Model/Version.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Class Version
11
+ */
12
+ class PipelinerSales_Model_Version
13
+ {
14
+ const EARLIEST_VERSION = 9;
15
+ const LATEST_VERSION = 15;
16
+
17
+ private static $entitiesToCollections = array(
18
+ 9 => array(
19
+ 'add' => array(
20
+ 'Account' => 'Accounts',
21
+ 'AccountType' => 'AccountTypes',
22
+ 'Activity' => 'Activities',
23
+ 'ActivityType' => 'ActivityTypes',
24
+ 'Appointment' => 'Appointments',
25
+ 'Client' => 'Clients',
26
+ 'Contact' => 'Contacts',
27
+ 'Currency' => 'Currencies',
28
+ 'Data' => 'Data',
29
+ 'Document' => 'Documents',
30
+ 'ExchangeRatesList' => 'ExRateLists',
31
+ 'Industry' => 'Industries',
32
+ 'IntegrationEnviroment' => 'IntegrationEnvs',
33
+ 'Lead' => 'Leads',
34
+ 'MasterRight' => 'MasterRights',
35
+ 'Message' => 'Messages',
36
+ 'Note' => 'Notes',
37
+ 'Opportunity' => 'Opportunities',
38
+ 'Product' => 'Products',
39
+ 'ReasonOfClose' => 'ReasonOfCloses',
40
+ 'Reminder' => 'Reminders',
41
+ 'SalesUnit' => 'SalesUnits',
42
+ 'Stage' => 'Stages'
43
+ )
44
+ ),
45
+ 11 => array(
46
+ 'add' => array(
47
+ 'Competence' => 'Competencies',
48
+ 'Relevance' => 'Relevancies'
49
+ )
50
+ ),
51
+ 12 => array(
52
+ 'add' => array(
53
+ 'Email' => 'Emails'
54
+ )
55
+ ),
56
+ 14 => array(
57
+ 'add' => array(
58
+ 'AddressbookRelation' => 'AddressbookRelations',
59
+ 'OpptyAccountRelation' => 'OpptyAccountRelations',
60
+ 'OpptyContactRelation' => 'OpptyContactRelations',
61
+ 'OpptyProductRelation' => 'OpptyProductRelations',
62
+ 'ProductCategory' => 'ProductCategories',
63
+ 'ProductPriceList' => 'ProductPriceLists',
64
+ 'ProductPriceListPrice' => 'ProductPriceListPrices'
65
+ )
66
+ ),
67
+ 15 => array(
68
+ 'add' => array(
69
+ 'OpptyContactRole' => 'OpptyContactRoles',
70
+ 'SalesRole' => 'SalesRoles'
71
+ ),
72
+ 'remove' => array(
73
+ 'Competence' => true,
74
+ 'Relevance' => true
75
+ )
76
+ )
77
+ );
78
+
79
+ /**
80
+ * @param $version
81
+ * @return array
82
+ */
83
+ public static function getEntityTypes($version)
84
+ {
85
+ $entityTypes = array();
86
+ for ($i = self::EARLIEST_VERSION; $i <= min(self::LATEST_VERSION, $version); $i++) {
87
+ if (!isset(self::$entitiesToCollections[$i])) {
88
+ continue;
89
+ }
90
+
91
+ if (isset(self::$entitiesToCollections[$i]['add'])) {
92
+ $entityTypes = array_merge($entityTypes, self::$entitiesToCollections[$i]['add']);
93
+ }
94
+
95
+ if (isset(self::$entitiesToCollections[$i]['remove'])) {
96
+ $entityTypes = array_diff_key($entityTypes, self::$entitiesToCollections[$i]['remove']);
97
+ }
98
+ }
99
+ return $entityTypes;
100
+ }
101
+ }
lib/PipelinerSales/PipelinerClient.php ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * The client context
11
+ *
12
+ * @property-read RepositoryInterface $accounts
13
+ * @property-read RepositoryInterface $accountTypes
14
+ * @property-read RepositoryInterface $activities
15
+ * @property-read RepositoryInterface $activityTypes
16
+ * @property-read RepositoryInterface $addressbookRelations (pipeline version 14+)
17
+ * @property-read RepositoryInterface $appointments
18
+ * @property-read RepositoryInterface $clients
19
+ * @property-read RepositoryInterface $competencies (pipeline version 11-14)
20
+ * @property-read RepositoryInterface $contacts
21
+ * @property-read RepositoryInterface $currencies
22
+ * @property-read RepositoryInterface $data
23
+ * @property-read RepositoryInterface $documents
24
+ * @property-read RepositoryInterface $emails (pipeline version 12+)
25
+ * @property-read RepositoryInterface $exRateLists
26
+ * @property-read RepositoryInterface $industries
27
+ * @property-read RepositoryInterface $integrationEnvs
28
+ * @property-read RepositoryInterface $leads
29
+ * @property-read RepositoryInterface $masterRights
30
+ * @property-read RepositoryInterface $messages
31
+ * @property-read RepositoryInterface $notes
32
+ * @property-read RepositoryInterface $opportunities
33
+ * @property-read RepositoryInterface $opptyAccountRelations (pipeline version 14+)
34
+ * @property-read RepositoryInterface $opptyContactRelations (pipeline version 14+)
35
+ * @property-read RepositoryInterface $opptyContactRoles (pipeline version 15+)
36
+ * @property-read RepositoryInterface $opptyProductRelations (pipeline version 14+)
37
+ * @property-read RepositoryInterface $products
38
+ * @property-read RepositoryInterface $productCategories (pipeline version 14+)
39
+ * @property-read RepositoryInterface $productPriceLists (pipeline version 14+)
40
+ * @property-read RepositoryInterface $productPriceListPrice (pipeline version 14+)
41
+ * @property-read RepositoryInterface $reasonOfCloses
42
+ * @property-read RepositoryInterface $relevancies (pipeline version 11-14)
43
+ * @property-read RepositoryInterface $reminders
44
+ * @property-read RepositoryInterface $salesRoles (pipeline version 15+)
45
+ * @property-read RepositoryInterface $salesUnits
46
+ * @property-read RepositoryInterface $stages
47
+ */
48
+ class PipelinerSales_PipelinerClient
49
+ {
50
+
51
+ private $entitiesToCollections;
52
+ private $collectionsToEntities;
53
+ private $repositories = array();
54
+ private $repositoryFactory;
55
+ private $infoMethods;
56
+ private $pipelineVersion;
57
+
58
+ /**
59
+ * Constructor. To create a client object configured for most typical uses, use the static {@see create} method.
60
+ *
61
+ * @param array $entityTypes an associative array with entity names as keys and their plurals as values
62
+ * @param RepositoryFactoryInterface $repositoryFactory
63
+ * @param InfoMethodsInterface $infoMethods
64
+ */
65
+ public function __construct(
66
+ array $entityTypes,
67
+ PipelinerSales_Repository_RepositoryFactoryInterface $repositoryFactory,
68
+ PipelinerSales_InfoMethodsInterface $infoMethods
69
+ ) {
70
+ $this->setEntityTypes($entityTypes);
71
+ $this->repositoryFactory = $repositoryFactory;
72
+ $this->infoMethods = $infoMethods;
73
+ }
74
+
75
+ /**
76
+ * Creates a PipelinerClient object with sensible default configuration.
77
+ * Will perform a HTTP request to fetch the pipeline version.
78
+ *
79
+ * @param string $url base url of the REST server, without the trailing slash
80
+ * @param string $pipelineId the unique team pipeline id
81
+ * @param string $apiToken API token
82
+ * @param string $password API password
83
+ * @return PipelinerClient
84
+ * @throws PipelinerClientException when trying to use an unsupported pipeline version
85
+ * @throws PipelinerHttpException if fetching the pipeline version fails
86
+ */
87
+ public static function create($url, $pipelineId, $apiToken, $password)
88
+ {
89
+ $baseUrl = $url . '/rest_services/v1/' . $pipelineId;
90
+
91
+ $httpClient = new PipelinerSales_Http_CurlHttpClient();
92
+ $httpClient->setUserCredentials($apiToken, $password);
93
+
94
+ $dateTimeFormat = PipelinerSales_Defaults::DATE_FORMAT;
95
+ $repoFactory = new PipelinerSales_Repository_Rest_RestRepositoryFactory($baseUrl, $httpClient, $dateTimeFormat);
96
+
97
+ $infoMethods = new PipelinerSales_Repository_Rest_RestInfoMethods($baseUrl, $httpClient);
98
+ $version = $infoMethods->fetchTeamPipelineVersion();
99
+
100
+ if ($version < PipelinerSales_Model_Version::EARLIEST_VERSION) {
101
+ throw new PipelinerSales_PipelinerClientException(
102
+ 'Unsupported team pipeline version: ' . $version .
103
+ ' (supported versions are ' . PipelinerSales_Model_Version::EARLIEST_VERSION . ' to ' . PipelinerSales_Model_Version::LATEST_VERSION . ')'
104
+ );
105
+ }
106
+
107
+ $entityTypes = PipelinerSales_Model_Version::getEntityTypes($version);
108
+
109
+ $client = new PipelinerSales_PipelinerClient($entityTypes, $repoFactory, $infoMethods);
110
+ $client->pipelineVersion = $version;
111
+ return $client;
112
+ }
113
+
114
+ /**
115
+ * Returns an associative array of recognized entity names to the plurals of their names
116
+ * @return array
117
+ */
118
+ public function getEntityTypes()
119
+ {
120
+ return $this->entitiesToCollections;
121
+ }
122
+
123
+ /**
124
+ * Returns an object for retrieving various information from the server.
125
+ * @return InfoMethodsInterface
126
+ */
127
+ public function getServerInfo()
128
+ {
129
+ return $this->infoMethods;
130
+ }
131
+
132
+ /**
133
+ * Magic getter for repositories. Calls {@see getRepository} for
134
+ * known entity types.
135
+ *
136
+ * @param string $name camelCase name of the collection (e.g. activityTypes)
137
+ * @return RepositoryInterface
138
+ */
139
+ public function __get($name)
140
+ {
141
+ $entityName = $this->collectionsToEntities[ucfirst($name)];
142
+ return $this->getRepository($entityName);
143
+ }
144
+
145
+ /**
146
+ * Returns a repository for the specified entity.
147
+ *
148
+ * @param mixed $entityName an {@see Entity} object or entity name,
149
+ * can be both singular (Account) and plural (Accounts)
150
+ * @return RepositoryInterface
151
+ */
152
+ public function getRepository($entityName)
153
+ {
154
+ if ($entityName instanceof PipelinerSales_Entity) {
155
+ $entityName = $entityName->getType();
156
+ }
157
+
158
+ if (!isset($this->repositories[$entityName])) {
159
+ $plural = $this->getCollectionName($entityName);
160
+ $this->repositories[$entityName] = $this->repositoryFactory->createRepository(
161
+ $entityName,
162
+ $plural
163
+ );
164
+ }
165
+
166
+ return $this->repositories[$entityName];
167
+ }
168
+
169
+ private function setEntityTypes($entitiesToCollections)
170
+ {
171
+ $this->entitiesToCollections = $entitiesToCollections;
172
+ $this->collectionsToEntities = array_flip($this->entitiesToCollections);
173
+ }
174
+
175
+ private function getCollectionName($entityName)
176
+ {
177
+ return $this->entitiesToCollections[$entityName];
178
+ }
179
+
180
+ public function getPipelineVersion()
181
+ {
182
+ return $this->pipelineVersion;
183
+ }
184
+
185
+ public function registeryEntityType($entityName, $collectionName)
186
+ {
187
+ $this->entitiesToCollections[$entityName] = $collectionName;
188
+ }
189
+ }
lib/PipelinerSales/PipelinerClientException.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ class PipelinerSales_PipelinerClientException extends \Exception
10
+ {
11
+
12
+ }
lib/PipelinerSales/Query/Criteria.php ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+
10
+ /**
11
+ * Represents the query parameters for loading entities.
12
+ *
13
+ * For details, see the {@link
14
+ * http://workspace.pipelinersales.com/community/api/data/Querying_rest.html
15
+ * API documentation on querying}.
16
+ *
17
+ * @method static Criteria limit($limit) Limits how many entities to load. Use the NO_LIMIT constant to disable limits.
18
+ * @method static Criteria offset($offset)
19
+ * @method static Criteria sort($sort) Set sorting of the result, either by using a raw string (see API documentation) or a Sort object
20
+ * @method static Criteria filter($filter) Only return entities which conform to the specified filter - either a raw string (see API documentation) or a Filter object
21
+ * @method static Criteria after($after) Only load entities modified after the date, specified as either a string or a DateTime object
22
+ * @method static Criteria loadonly($loadonly) Only load the specified fields, specified as either an array or a string separated with |
23
+ */
24
+ class PipelinerSales_Query_Criteria
25
+ {
26
+
27
+ private static $properties = array(
28
+ 'limit' => 'setLimit',
29
+ 'offset' => 'setOffset',
30
+ 'sort' => 'setSort',
31
+ 'filter' => 'setFilter',
32
+ 'after' => 'setAfter',
33
+ 'loadonly' => 'setLoadOnly'
34
+ );
35
+ private $limit = null;
36
+ private $offset = null;
37
+ private $sort = null;
38
+ private $filter = null;
39
+ private $after = null;
40
+ private $loadonly = null;
41
+ private $dateTimeFormat;
42
+ private $defaultLimit;
43
+
44
+ /**
45
+ * Disable the limit on how many entities to load. Please use with caution.
46
+ */
47
+ const NO_LIMIT = -1;
48
+
49
+ /**
50
+ * Creates a new set of criteria.
51
+ * @param mixed $criteria initial criteria to use, see {@see set}
52
+ * @param string $dateTimeFormat format to use for converting DateTime objects
53
+ */
54
+ public function __construct($criteria = array(), $dateTimeFormat = PipelinerSales_Defaults::DATE_FORMAT, $defaultLimit = PipelinerSales_Defaults::DEFAULT_LIMIT)
55
+ {
56
+ $this->dateTimeFormat = $dateTimeFormat;
57
+ $this->defaultLimit = $defaultLimit;
58
+ $this->set($criteria);
59
+ }
60
+
61
+ /**
62
+ * Returns the resulting url-encoded query string.
63
+ * @return string
64
+ */
65
+ public function toUrlQuery()
66
+ {
67
+ $queryData = array();
68
+ foreach (self::$properties as $p => $setter) {
69
+ if ($this->$p !== null) {
70
+ $queryData[$p] = $this->$p;
71
+ }
72
+ }
73
+ return http_build_query($queryData);
74
+ }
75
+
76
+ /**
77
+ * Returns the currently set limit. Null will be returned if the limit is not set.
78
+ * @return integer|null
79
+ */
80
+ public function getLimit()
81
+ {
82
+ return $this->limit;
83
+ }
84
+
85
+ /**
86
+ * Returns the currently set limit, or the default limit if none is set.
87
+ * @return integer
88
+ */
89
+ public function getEffectiveLimit()
90
+ {
91
+ if ($this->limit === null) {
92
+ return $this->defaultLimit;
93
+ }
94
+ return $this->limit;
95
+ }
96
+
97
+ /**
98
+ * @param integer $limit
99
+ * @return Criteria
100
+ */
101
+ private function setLimit($limit)
102
+ {
103
+ $this->limit = $limit;
104
+ return $this;
105
+ }
106
+
107
+ /**
108
+ * Returns the currently set offset. Null will be returned if no offset is set.
109
+ * @return integer|null
110
+ */
111
+ public function getOffset()
112
+ {
113
+ return $this->offset;
114
+ }
115
+
116
+ /**
117
+ * Returns the currently set offset, or the default offset 0 if no offset is set.
118
+ * @return integer
119
+ */
120
+ public function getEffectiveOffset()
121
+ {
122
+ if ($this->offset === null) {
123
+ return 0;
124
+ }
125
+ return $this->getOffset();
126
+ }
127
+
128
+ /**
129
+ * @param integer $offset
130
+ * @return Criteria
131
+ */
132
+ private function setOffset($offset)
133
+ {
134
+ $this->offset = $offset;
135
+ return $this;
136
+ }
137
+
138
+ /**
139
+ * @return string|null
140
+ */
141
+ public function getSort()
142
+ {
143
+ return $this->sort;
144
+ }
145
+
146
+ /**
147
+ * @param string|Sort
148
+ * @return Criteria
149
+ */
150
+ public function setSort($sort)
151
+ {
152
+ if ($sort instanceof PipelinerSales_Query_Sort) {
153
+ $this->sort = $sort->getString();
154
+ } else {
155
+ $this->sort = $sort;
156
+ }
157
+ return $this;
158
+ }
159
+
160
+ /**
161
+ * Returns the currently set filter string.
162
+ * @return string|null
163
+ */
164
+ public function getFilter()
165
+ {
166
+ return $this->filter;
167
+ }
168
+
169
+ /**
170
+ * @param Filter|string
171
+ * @return Criteria
172
+ */
173
+ private function setFilter($filter)
174
+ {
175
+ if ($filter instanceof PipelinerSales_Query_Filter) {
176
+ $this->filter = $filter->getString();
177
+ } else {
178
+ $this->filter = $filter;
179
+ }
180
+ return $this;
181
+ }
182
+
183
+ /**
184
+ * @return string|null
185
+ */
186
+ public function getAfter()
187
+ {
188
+ return $this->after;
189
+ }
190
+
191
+ /**
192
+ * @param string|\DateTime $after
193
+ * @return Criteria
194
+ */
195
+ private function setAfter($after)
196
+ {
197
+ if (is_string($after)) {
198
+ $this->after = $after;
199
+ } elseif ($after instanceof \DateTime) {
200
+ $afterCopy = clone $after;
201
+ $afterCopy->setTimezone(new \DateTimeZone('UTC'));
202
+ $this->after = $afterCopy->format($this->dateTimeFormat);
203
+ }
204
+ return $this;
205
+ }
206
+
207
+ /**
208
+ * @return string|null
209
+ */
210
+ public function getLoadOnly()
211
+ {
212
+ return $this->loadonly;
213
+ }
214
+
215
+ /**
216
+ * @param string|array
217
+ * @return Criteria
218
+ */
219
+ private function setLoadOnly($loadonly)
220
+ {
221
+ if (is_string($loadonly)) {
222
+ $this->loadonly = $loadonly;
223
+ } elseif (is_array($loadonly)) {
224
+ $this->loadonly = implode('|', $loadonly);
225
+ }
226
+ return $this;
227
+ }
228
+
229
+ /**
230
+ * Sets multiple parameters at once.
231
+ *
232
+ * @param mixed $criteria Can be one of the following:
233
+ * <ul>
234
+ * <li>Another Criteria object - copies that object's criteria into this one.
235
+ * Competely replaces the current object's values, unsetting those which have not been
236
+ * set in $criteria</li>
237
+ * <li>An array - sets the parameters present in the array. Parameters not present
238
+ * in the array will <b>not</b> be unset. If you wish to unset certain parameters,
239
+ * set them to null in the array.</li>
240
+ * <li>A string - the resulting query string (without a leading question mark),
241
+ * will be parsed back into the individual parameters and processed like an array.</li>
242
+ * </ul>
243
+ */
244
+ public function set($criteria)
245
+ {
246
+ if ($criteria instanceof PipelinerSales_Query_Criteria) {
247
+ //this is much faster than using the same method that's used for arrays (see below)
248
+ $this->limit = $criteria->limit;
249
+ $this->offset = $criteria->offset;
250
+ $this->sort = $criteria->sort;
251
+ $this->filter = $criteria->filter;
252
+ $this->after = $criteria->after;
253
+ $this->loadonly = $criteria->loadonly;
254
+ } elseif (is_string($criteria)) {
255
+ //parse the string into an array and then parse the array
256
+ $criteria = $this->parseHttpQuery($criteria);
257
+ }
258
+
259
+ if (is_array($criteria)) {
260
+ foreach (self::$properties as $p => $setter) {
261
+ if (isset($criteria[$p])) {
262
+ $this->$setter($criteria[$p]);
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Parses a http query into a key-value array, similar to PHP's parse_str function.
270
+ * @param string $query query string (without a leading question mark)
271
+ * @return array
272
+ */
273
+ private function parseHttpQuery($query)
274
+ {
275
+ /* can't use parse_str, because the minimum supported PHP version
276
+ * is 5.3, which still has the magic_quotes_gpc option that affects its
277
+ * result, so some users could hypothetically have that enabled */
278
+ if (empty($query)) {
279
+ return array();
280
+ }
281
+
282
+ $result = array();
283
+ $parts = explode('&', $query);
284
+ foreach ($parts as $p) {
285
+ $keyval = explode('=', $p);
286
+ $result[urldecode($keyval[0])] = urldecode($keyval[1]);
287
+ }
288
+ return $result;
289
+ }
290
+
291
+ public static function __callStatic($name, $arguments)
292
+ {
293
+ if (isset(self::$properties[$name])) {
294
+ return new PipelinerSales_Query_Criteria(array( $name => $arguments[0] ));
295
+ }
296
+ throw new \BadMethodCallException('Call to a non-existent static method \'' . $name . '\'');
297
+ }
298
+
299
+ public function __call($name, $arguments)
300
+ {
301
+ if (isset(self::$properties[$name])) {
302
+ $setter = self::$properties[$name];
303
+ $this->$setter($arguments[0]);
304
+ return $this;
305
+ }
306
+ throw new \BadMethodCallException('Call to a non-existent method \'' . $name . '\'');
307
+ }
308
+ }
lib/PipelinerSales/Query/Filter.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * A conventient wrapper for building the filter string used in queries.
11
+ *
12
+ * For details, see the {@link
13
+ * http://workspace.pipelinersales.com/community/api/data/Querying_rest.html
14
+ * API documentation on querying}.
15
+ *
16
+ * All the magic methods can also be called statically, e.g.
17
+ * <code>
18
+ * Filter::equals("NAME", "Joe")->greaterThan("HEIGHT", 0)
19
+ * </code>
20
+ *
21
+ * For most operators (all except ll, rl, fl and their aliases), DateTime objects can be
22
+ * used as value.
23
+ *
24
+ * @method static Filter eq(string $fieldName, mixed $value) field <b>equals</b> value
25
+ * @method static Filter equals(string $fieldName, mixed $value) alias for {@see eq}
26
+ * @method static Filter ne(string $fieldName, mixed $value) field <b>does not equal</b> value
27
+ * @method static Filter doesNotEqual(string $fieldName, mixed $value) alias for {@see ne}
28
+ * @method static Filter gt(string $fieldName, mixed $value) field is <b>greater than</b> value
29
+ * @method static Filter greaterThan(string $fieldName, mixed $value) alias for {@see gt}
30
+ * @method static Filter lt(string $fieldName, mixed $value) field is <b>less than</b> value
31
+ * @method static Filter lessThan(string $fieldName, mixed $value) alias for {@see lt}
32
+ * @method static Filter ge(string $fieldName, mixed $value) field is <b>greater or equal</b> to value
33
+ * @method static Filter gte(string $fieldName, mixed $value) alias for {@see ge}
34
+ * @method static Filter greaterOrEqual(string $fieldName, mixed $value) alias for {@see ge}
35
+ * @method static Filter le(string $fieldName, mixed $value) field is <b>less or equal</b> to value
36
+ * @method static Filter lte(string $fieldName, mixed $value) alias for {@see le}
37
+ * @method static Filter lessOrEqual(string $fieldName, mixed $value) alias for {@see le}
38
+ * @method static Filter ll(string $fieldName, string $value) field starts with value
39
+ * @method static Filter startsWith(string $fieldName, string $value) alias for {@see ll}
40
+ * @method static Filter rl(string $fieldName, string $value) field ends with value
41
+ * @method static Filter endsWith(string $fieldName, string $value) alias for {@see rl}
42
+ * @method static Filter fl(string $fieldName, string $value) field contains value
43
+ * @method static Filter contains(string $fieldName, string $value) alias for {@see fl}
44
+ * @method static Filter raw(string $filterString) appends a separator &#40;|&#41; followed
45
+ * by the raw filter string to the current filter string
46
+ */
47
+ class PipelinerSales_Query_Filter
48
+ {
49
+
50
+ private $filterString;
51
+ private $dateTimeFormat;
52
+ private static $operators = array(
53
+ 'eq' => 'eq',
54
+ 'equals' => 'eq',
55
+ 'ne' => 'ne',
56
+ 'doesNotEqual' => 'ne',
57
+ 'gt' => 'gt',
58
+ 'greaterThan' => 'gt',
59
+ 'lt' => 'lt',
60
+ 'lessThan' => 'lt',
61
+ 'ge' => 'ge',
62
+ 'gte' => 'ge',
63
+ 'greaterOrEqual' => 'ge',
64
+ 'le' => 'le',
65
+ 'lte' => 'le',
66
+ 'lessOrEqual' => 'le',
67
+ 'll' => 'll',
68
+ 'startsWith' => 'll',
69
+ 'rl' => 'rl',
70
+ 'endsWith', 'rl',
71
+ 'fl' => 'fl',
72
+ 'contains' => 'fl',
73
+ 'raw' => 'raw'
74
+ );
75
+
76
+ public function __construct($filter = '', $dateTimeFormat = PipelinerSales_Defaults::DATE_FORMAT)
77
+ {
78
+ if ($filter instanceof PipelinerSales_Query_Filter) {
79
+ $this->filterString = $filter->filterString;
80
+ } else {
81
+ $this->filterString = $filter;
82
+ }
83
+ }
84
+
85
+ public function __call($name, $arguments)
86
+ {
87
+ if (isset(self::$operators[$name])) {
88
+ if (!empty($this->filterString)) {
89
+ $this->filterString .= '|';
90
+ }
91
+
92
+ if (self::$operators[$name] == 'raw') {
93
+ $this->filterString .= $arguments[0];
94
+ } else {
95
+ if ($arguments[1] instanceof \DateTime) {
96
+ $dateTimeCopy = clone $arguments[1];
97
+ $dateTimeCopy->setTimezone(new \DateTimeZone('UTC'));
98
+ $arguments[1] = $dateTimeCopy->format($this->dateTimeFormat);
99
+ }
100
+
101
+ $this->filterString .= $arguments[0] . '::' . $arguments[1];
102
+ if (self::$operators[$name] != 'eq') {
103
+ $this->filterString .= '::' . self::$operators[$name];
104
+ }
105
+ }
106
+
107
+ return $this;
108
+ } else {
109
+ throw new PipelinerClientException('Invalid filter operator: ' . $name);
110
+ }
111
+ }
112
+
113
+ public static function __callStatic($name, $arguments)
114
+ {
115
+ if (isset(self::$operators[$name])) {
116
+ $filter = new PipelinerSales_Query_Filter();
117
+ $filter->$name($arguments[0], $arguments[1]);
118
+ return $filter;
119
+ } else {
120
+ throw new PipelinerClientException('Invalid filter operator: ' . $name);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Returns the resulting filter string usable in query
126
+ * @return string
127
+ */
128
+ public function getString()
129
+ {
130
+ return $this->filterString;
131
+ }
132
+ }
lib/PipelinerSales/Query/Sort.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * A convenient wrapper for generating the sort string for queries.
11
+ *
12
+ * For details, see the {@link http://workspace.pipelinersales.com/community/api/data/Querying_rest.html
13
+ * API documentation}.
14
+ *
15
+ * All the magic methods can also be called statically, e.g.
16
+ * <code>
17
+ * Sort::asc('NAME')->desc('MODIFIED')
18
+ * </code>
19
+ *
20
+ * @method static Sort asc(string $fieldName) sorts by a field in ascending order
21
+ * @method static Sort desc(string $fieldName) sorts by a field in descending order
22
+ * @method static Sort raw(string $sortString) appends a separator &#40;|&#41; followed
23
+ * by a raw string to the current sort string
24
+ */
25
+ class PipelinerSales_Query_Sort
26
+ {
27
+
28
+ private $sortString = '';
29
+
30
+ /**
31
+ * Constructor, optionally copies an existing sort string into this object.
32
+ * @param mixed $sort either a sort string or another Sort object
33
+ */
34
+ public function __construct($sort = '')
35
+ {
36
+ if ($sort instanceof PipelinerSales_Query_Sort) {
37
+ $this->sortString = $sort->sortString;
38
+ } else {
39
+ $this->raw($sort);
40
+ }
41
+ }
42
+
43
+ public function __call($name, $arguments)
44
+ {
45
+ if ($name === 'asc' or $name === 'desc' or $name === 'raw') {
46
+ if (!empty($this->sortString)) {
47
+ $this->sortString .= '|';
48
+ }
49
+
50
+ if ($name === 'desc') {
51
+ $this->sortString .= '-';
52
+ }
53
+
54
+ $this->sortString .= $arguments[0];
55
+ return $this;
56
+ }
57
+ throw new \BadMethodCallException('Call to a non-existent method \'' . $name . '\'');
58
+ }
59
+
60
+ public static function __callStatic($name, $arguments)
61
+ {
62
+ if ($name === 'asc' or $name === 'desc' or $name === 'raw') {
63
+ $sort = new PipelinerSales_Query_Sort();
64
+ $sort->$name($arguments[0]);
65
+ return $sort;
66
+ }
67
+ throw new \BadMethodCallException('Call to a non-existent static method \'' . $name . '\'');
68
+ }
69
+
70
+ /**
71
+ * Returns the resulting sort string.
72
+ * @return string
73
+ */
74
+ public function getString()
75
+ {
76
+ return $this->sortString;
77
+ }
78
+ }
lib/PipelinerSales/Repository/RepositoryFactoryInterface.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ interface PipelinerSales_Repository_RepositoryFactoryInterface
10
+ {
11
+ /**
12
+ * @param string $entitySingular
13
+ * @param string $entityPlural
14
+ * @return RepositoryInterface
15
+ */
16
+ public function createRepository($entitySingular, $entityPlural);
17
+ }
lib/PipelinerSales/Repository/RepositoryInterface.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * An interface for retrieving and manipulating entities.
11
+ */
12
+ interface PipelinerSales_Repository_RepositoryInterface
13
+ {
14
+
15
+ // flags for batch operations:
16
+
17
+ /** If any error occurs, no entity will be processed, and the entire batch will be rolled back. */
18
+ const FLAG_ROLLBACK_ON_ERROR = 0;
19
+
20
+ /** If an error occurs for an entity, the entity is ignored and the system continues with the next one. */
21
+ const FLAG_IGNORE_ON_ERROR = 1;
22
+
23
+ /** If any error occurs during the update of an entity (e.g. the entity doesn’t exists),
24
+ * the entity will be inserted instead, and a new unique identificator will be generated */
25
+ const FLAG_INSERT_ON_UPDATE = 2;
26
+
27
+ /** The method will return a list of IDs which cannot be deleted.
28
+ * Can be used with combination with FLAG_IGNORE_ON_ERROR. */
29
+ const FLAG_GET_NO_DELETED_ID = 4;
30
+
31
+ /** If any error occurs for an entity, the entity is ignored and the system continues with the next one. */
32
+ const FLAG_IGNORE_AND_RETURN_ERRORS = 8;
33
+
34
+ /** Only updated fields in the entity will be validated instead of all fields. */
35
+ const FLAG_VALIDATE_ONLY_UPDATED_FIELDS = 256;
36
+
37
+ // flags for save operations about which fields to send:
38
+
39
+ /** Partial update, send only fields which have been modified since the entity was last loaded/saved */
40
+ const SEND_MODIFIED_FIELDS = 0;
41
+
42
+ /** Send all fields that exist within the entity */
43
+ const SEND_ALL_FIELDS = 1;
44
+
45
+ /**
46
+ * Creates a new entity. Please note that this entity won't exist on the
47
+ * server until you save it by calling the save method.
48
+ *
49
+ * @return Entity
50
+ */
51
+ public function create();
52
+
53
+ /**
54
+ * Deletes an entity
55
+ *
56
+ * @param mixed $entity the entity to delete, or an array of multiple entities
57
+ * @param integer $flags used only if multiple entities are provided,
58
+ * flags described on {@link http://workspace.pipelinersales.com/community/api/data/Methods_rest.html}
59
+ * @return Response response to the delete HTTP request
60
+ * @throws PipelinerHttpException
61
+ * @throws PipelinerClientException when the entity provided doesn't have an ID,
62
+ * which usually means that it's a newly created entity that wasn't saved yet
63
+ */
64
+ public function delete($entity, $flags = self::FLAG_ROLLBACK_ON_ERROR);
65
+
66
+ /**
67
+ * Deletes one or more entities specified by their ID
68
+ *
69
+ * @param mixed $id the ID of the entity to delete, or an array containing multiple IDs
70
+ * @param integer $flags used only if multiple IDs are provided,
71
+ * flags described on {@link http://workspace.pipelinersales.com/community/api/data/Querying_rest.html}
72
+ * @return Response response to the delete HTTP request
73
+ * @throws PipelinerHttpException
74
+ */
75
+ public function deleteById($id, $flags = 0);
76
+
77
+ /**
78
+ * Returns a collection of all entities satisfying the provided criteria
79
+ *
80
+ * @param mixed $criteria Can be one of the following
81
+ * <ul>
82
+ * <li>null - fetches all entities up to a default limit of 25</li>
83
+ * <li>a {@see Criteria} object</li>
84
+ * <li>a {@see Filter} object</li>
85
+ * <li>a {@see Sort} object</li>
86
+ * <li>a query string following the format described at
87
+ * {@link http://workspace.pipelinersales.com/community/api/data/Querying_rest.html}</li>
88
+ * <li>an array with keys corresponding to the format at
89
+ * {@link http://workspace.pipelinersales.com/community/api/data/Querying_rest.html}</li>
90
+ * </ul>
91
+ * @return EntityCollection
92
+ * @throws PipelinerHttpException when retrieving data from the server fails
93
+ * @throws PipelinerClientException when provided criteria is invalid
94
+ */
95
+ public function get($criteria = null);
96
+
97
+ /**
98
+ * Returns an entity with the specified ID
99
+ *
100
+ * @param string $id
101
+ * @throws PipelinerHttpException
102
+ * @return Entity
103
+ */
104
+ public function getById($id);
105
+
106
+ /**
107
+ * Uploads an entity to the server.
108
+ *
109
+ * @param mixed $entity the entity to upload, an {@see Entity} object or an associative array
110
+ * @param integer $sendFields whether to upload all the fields for Entity objects, or just the fields
111
+ * which have been modified in code since the entity was loaded/saved
112
+ * @return Response|CreatedResponse response to the HTTP request
113
+ * @throws PipelinerHttpException
114
+ */
115
+ public function save($entity, $sendFields = self::SEND_MODIFIED_FIELDS);
116
+
117
+ /**
118
+ * Updates multiple entities at once
119
+ *
120
+ * @param mixed $data entities to update, where each entity is either an entity object or an associative array
121
+ * with keys corresponding to fields and values to the entity's values.
122
+ * See the setEntities method at
123
+ * {@link http://workspace.pipelinersales.com/community/api/data/Methods_rest.html}
124
+ * @param integer $flags flags described on
125
+ * {@link http://workspace.pipelinersales.com/community/api/data/Methods_rest.html}
126
+ * @param integer $sendFields whether to upload all the fields for Entity objects, or just the fields
127
+ * which have been modified in code since the entity was loaded/saved
128
+ * @return Response response to the HTTP request
129
+ * @throws PipelinerHttpException
130
+ */
131
+ public function bulkUpdate($data, $flags = self::FLAG_ROLLBACK_ON_ERROR, $sendFields = self::SEND_MODIFIED_FIELDS);
132
+
133
+ /**
134
+ * Returns an iterator set to the offset of the query's criteria
135
+ *
136
+ * @param EntityCollection $collection
137
+ * @return EntityCollectionIterator
138
+ */
139
+ public function getEntireRangeIterator(PipelinerSales_EntityCollection $collection);
140
+ }
lib/PipelinerSales/Repository/Rest/RestCreatedResponse.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ class PipelinerSales_Repository_Rest_RestCreatedResponse extends PipelinerSales_Http_CreatedResponse
10
+ {
11
+ public function __construct(PipelinerSales_Http_Response $r)
12
+ {
13
+ parent::__construct(
14
+ $r->getBody(),
15
+ $r->getHeaders(),
16
+ $r->getStatusCode(),
17
+ $r->getRequestUrl(),
18
+ $r->getRequestMethod()
19
+ );
20
+ }
21
+
22
+ public function getCreatedId()
23
+ {
24
+ //Response to a POST request creating a new entity has a Location header
25
+ //which contains the URL of that entity. Its ID is after the last slash
26
+ //in that URL.
27
+ $headers = $this->getHeaders();
28
+ $locationPos = strpos($headers, "\r\nLocation:");
29
+ $lineEndPos = strpos($headers, "\r\n", $locationPos + 1);
30
+ $locationLine = substr($headers, $locationPos, $lineEndPos - $locationPos);
31
+ $finalSlashPos = strrpos($locationLine, '/');
32
+ return substr($locationLine, $finalSlashPos + 1);
33
+ }
34
+ }
lib/PipelinerSales/Repository/Rest/RestInfoMethods.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ class PipelinerSales_Repository_Rest_RestInfoMethods implements PipelinerSales_InfoMethodsInterface
10
+ {
11
+
12
+ private $baseUrl;
13
+ private $httpClient;
14
+
15
+ public function __construct($baseUrl, PipelinerSales_Http_HttpInterface $httpClient)
16
+ {
17
+ $this->baseUrl = $baseUrl;
18
+ $this->httpClient = $httpClient;
19
+ }
20
+
21
+ public function fetchTeamPipelineUrl()
22
+ {
23
+ return $this->httpClient->request('GET', $this->baseUrl . '/teamPipelineUrl')->decodeJson(true);
24
+ }
25
+
26
+ public function fetchTeamPipelineVersion()
27
+ {
28
+ return $this->httpClient->request('GET', $this->baseUrl . '/teamPipelineVersion')->decodeJson(true);
29
+ }
30
+
31
+ public function fetchServerAPIUtcDateTime()
32
+ {
33
+ return $this->httpClient->request('GET', $this->baseUrl . '/serverAPIUtcDateTime')->decodeJson(true);
34
+ }
35
+
36
+ public function fetchErrorCodes()
37
+ {
38
+ return $this->httpClient->request('GET', $this->baseUrl . '/errorCodes')->decodeJson(true);
39
+ }
40
+
41
+ public function fetchCollections()
42
+ {
43
+ //not / as described in the docs (no / at the end)
44
+ return $this->httpClient->request('GET', $this->baseUrl)->decodeJson(true);
45
+ }
46
+
47
+ public function fetchEntityPublic()
48
+ {
49
+ return $this->httpClient->request('GET', $this->baseUrl . '/entityPublic')->decodeJson(true);
50
+ }
51
+
52
+ public function fetchEntityFields($entityName)
53
+ {
54
+ return $this->httpClient->request('GET', $this->baseUrl . '/getFields/' . $entityName)->decodeJson(true);
55
+ }
56
+
57
+ public function getHttpClient()
58
+ {
59
+ return $this->httpClient;
60
+ }
61
+
62
+ public function getBaseUrl()
63
+ {
64
+ return $this->baseUrl;
65
+ }
66
+ }
lib/PipelinerSales/Repository/Rest/RestRepository.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ /**
10
+ * Class for retrieving and manipulating entities using Pipeliner's REST API.
11
+ */
12
+ class PipelinerSales_Repository_Rest_RestRepository implements PipelinerSales_Repository_RepositoryInterface
13
+ {
14
+
15
+ private $httpClient;
16
+ private $urlPrefix;
17
+ private $entityType;
18
+ private $entityPlural;
19
+ private $dateTimeFormat;
20
+
21
+ public function __construct($urlPrefix, $entity, $entityPlural, PipelinerSales_Http_HttpInterface $connection, $dateTimeFormat)
22
+ {
23
+ $this->httpClient = $connection;
24
+ $this->urlPrefix = $urlPrefix;
25
+ $this->entityType = $entity;
26
+ $this->entityPlural = $entityPlural;
27
+ $this->dateTimeFormat = $dateTimeFormat;
28
+ }
29
+
30
+ public function getById($id)
31
+ {
32
+ $result = $this->httpClient
33
+ ->request('GET', $this->urlPrefix . '/' . $this->entityPlural . '/' . $id)
34
+ ->decodeJson();
35
+ return $this->decodeEntity($result);
36
+ }
37
+
38
+ public function get($criteria = null)
39
+ {
40
+ $criteriaUrl = '?';
41
+ if (is_null($criteria)) {
42
+ $criteriaUrl = '';
43
+ } elseif (is_array($criteria)) {
44
+ $criteriaUrl .= $this->arrayCriteriaToUrl($criteria);
45
+ } elseif (is_object($criteria)) {
46
+ $criteriaUrl .= $this->objectCriteriaToUrl($criteria);
47
+ } elseif (is_string($criteria)) {
48
+ $criteriaUrl .= $criteria;
49
+ } else {
50
+ throw new PipelinerSales_PipelinerClientException('Invalid criteria type');
51
+ }
52
+
53
+ $response = $this->httpClient
54
+ ->request('GET', $this->urlPrefix . '/' . $this->entityPlural . $criteriaUrl);
55
+ $results = $response->decodeJson();
56
+ $decoded = array_map(array($this, 'decodeEntity'), $results);
57
+
58
+ $range = $this->getContentRange($response);
59
+ if ($range === null) {
60
+ throw new PipelinerSales_Http_PipelinerHttpException($response, 'Content-Range header not found');
61
+ }
62
+
63
+ return new PipelinerSales_EntityCollection($decoded, $criteria, $range['start'], $range['end'], $range['total']);
64
+ }
65
+
66
+ public function create()
67
+ {
68
+ return new PipelinerSales_Entity($this->entityType, $this->dateTimeFormat);
69
+ }
70
+
71
+ public function delete($entity, $flags = self::FLAG_ROLLBACK_ON_ERROR)
72
+ {
73
+ //if $entity is an array, it can either be a single entity
74
+ //represented as an associative array, or an array of multiple entities,
75
+ //i.e. [ 'ID' => 'Something' ] vs. [ [ 'ID' => 'Something1' ], [ 'ID' => 'Something2 ] ],
76
+ //so we need to distinguish between these two cases
77
+ if (is_array($entity)) {
78
+ $first = reset($entity);
79
+
80
+ //if the first element of $entity is also an array,
81
+ //we are dealing with multiple entities
82
+ if (is_array($first) or $first instanceof PipelinerSales_Entity) {
83
+ return $this->deleteById(array_map(function ($item) {
84
+ return $item['ID'];
85
+ }, $entity), $flags);
86
+ }
87
+ }
88
+
89
+ if (!isset($entity['ID'])) {
90
+ throw new PipelinerSales_PipelinerClientException('Cannot delete an entity which has no ID');
91
+ }
92
+ return $this->deleteById($entity['ID']);
93
+ }
94
+
95
+ public function deleteById($id, $flags = self::FLAG_ROLLBACK_ON_ERROR)
96
+ {
97
+ if (is_array($id)) {
98
+ //bulk delete
99
+ $json = json_encode($id);
100
+ $url = $this->urlPrefix . '/deleteEntities?entityName=' . $this->entityType;
101
+ if ($flags) {
102
+ $url .= '&flag=' . $flags;
103
+ }
104
+ return $this->httpClient->request('POST', $url, $json);
105
+ } else {
106
+ return $this->httpClient->request('DELETE', $this->urlPrefix . '/' . $this->entityPlural . '/' . $id);
107
+ }
108
+ }
109
+
110
+ public function bulkUpdate($data, $flags = self::FLAG_ROLLBACK_ON_ERROR, $sendFields = self::SEND_MODIFIED_FIELDS)
111
+ {
112
+ $payload = array();
113
+
114
+ foreach ($data as $entity) {
115
+ if ($entity instanceof PipelinerSales_Entity) {
116
+ $values = (
117
+ $sendFields == self::SEND_MODIFIED_FIELDS ? $entity->getModifiedFields() : $entity->getFields()
118
+ );
119
+ $values['ID'] = $entity->getId();
120
+ $payload[] = $values;
121
+ } else {
122
+ $payload[] = $entity;
123
+ }
124
+ }
125
+
126
+ $json = json_encode($payload);
127
+ $url = $this->urlPrefix . '/setEntities?entityName=' . $this->entityType;
128
+ if ($flags) {
129
+ $url .= '&flag=' . $flags;
130
+ }
131
+ return $this->httpClient->request('POST', $url, $json);
132
+ }
133
+
134
+ public function save($entity, $sendFields = self::SEND_MODIFIED_FIELDS)
135
+ {
136
+ if ($entity instanceof PipelinerSales_Entity) {
137
+ $id = $entity->isFieldSet('ID') ? $entity->getId() : null;
138
+ $data = ($sendFields == self::SEND_MODIFIED_FIELDS ? $entity->modifiedToJson() : $entity->allToJson());
139
+ } elseif (is_array($entity)) {
140
+ $id = isset($entity['ID']) ? $entity['ID'] : null;
141
+ $data = json_encode($entity);
142
+ } else {
143
+ throw new \InvalidArgumentException('Invalid entity');
144
+ }
145
+
146
+ if (!empty($id)) {
147
+ //The server currently interprets both of these:
148
+ // - POST to /Accounts, where the entity contains an ID
149
+ // - PUT to /Accounts/{id}
150
+ //as partial updates.
151
+ //Full PUT replacement is not implemented on the server.
152
+ $method = 'PUT';
153
+ $url = $this->urlPrefix . '/' . $this->entityPlural . '/' . $id;
154
+ } else {
155
+ $method = 'POST';
156
+ $url = $this->urlPrefix . '/' . $this->entityPlural;
157
+ }
158
+
159
+ $response = $this->httpClient->request($method, $url, $data);
160
+
161
+ //new entity was created, we need to get its ID
162
+ if ($response->getStatusCode() == 201) {
163
+ $response = new PipelinerSales_Repository_Rest_RestCreatedResponse($response);
164
+ $newId = $response->getCreatedId();
165
+ if ($entity instanceof PipelinerSales_Entity) {
166
+ $entity->setId($newId);
167
+ }
168
+ }
169
+
170
+ if (($response->getStatusCode() == 200 or $response->getStatusCode() == 201)
171
+ and ( $entity instanceof PipelinerSales_Entity )) {
172
+ $entity->resetModified();
173
+ }
174
+
175
+ return $response;
176
+ }
177
+
178
+ public function getEntireRangeIterator(PipelinerSales_EntityCollection $collection)
179
+ {
180
+ return new PipelinerSales_EntityCollectionIterator($this, $collection);
181
+ }
182
+
183
+ private function arrayCriteriaToUrl(array $criteria)
184
+ {
185
+ return http_build_query($criteria);
186
+ }
187
+
188
+ private function objectCriteriaToUrl($criteria)
189
+ {
190
+ if ($criteria instanceof PipelinerSales_Query_Criteria) {
191
+ return $criteria->toUrlQuery();
192
+ } elseif ($criteria instanceof PipelinerSales_Query_Filter) {
193
+ $c = new PipelinerSales_Query_Criteria();
194
+ $c->filter($criteria);
195
+ return $c->toUrlQuery();
196
+ } elseif ($criteria instanceof PipelinerSales_Query_Sort) {
197
+ $c = new PipelinerSales_Query_Criteria();
198
+ $c->sort($criteria);
199
+ return $c->toUrlQuery();
200
+ }
201
+ throw new PipelinerSales_PipelinerClientException('Invalid criteria object');
202
+ }
203
+
204
+ private function decodeEntity(\stdClass $object)
205
+ {
206
+ $entity = new PipelinerSales_Entity($this->entityType, $this->dateTimeFormat);
207
+
208
+ foreach ($object as $field => $value) {
209
+ $entity->setField($field, $value);
210
+ }
211
+ $entity->resetModified();
212
+
213
+ return $entity;
214
+ }
215
+
216
+ private function getContentRange(PipelinerSales_Http_Response $response)
217
+ {
218
+ $result = preg_match('~Content-Range: items (\d+)-([\d-]+)/(\d+)~i', $response->getHeaders(), $matches);
219
+ if (!$result) {
220
+ return null;
221
+ }
222
+
223
+ return array(
224
+ 'start' => intval($matches[1]),
225
+ 'end' => intval($matches[2]),
226
+ 'total' => intval($matches[3])
227
+ );
228
+ }
229
+ }
lib/PipelinerSales/Repository/Rest/RestRepositoryFactory.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file is part of the Pipeliner API client library for PHP
4
+ *
5
+ * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
6
+ * For the full license information, see the attached LICENSE file.
7
+ */
8
+
9
+ class PipelinerSales_Repository_Rest_RestRepositoryFactory implements PipelinerSales_Repository_RepositoryFactoryInterface
10
+ {
11
+ private $baseUrl;
12
+ private $httpClient;
13
+ private $dateTimeFormat;
14
+
15
+ public function __construct($baseUrl, $httpClient, $dateTimeFormat)
16
+ {
17
+ $this->baseUrl = $baseUrl;
18
+ $this->httpClient = $httpClient;
19
+ $this->dateTimeFormat = $dateTimeFormat;
20
+ }
21
+
22
+ public function createRepository($entitySingular, $entityPlural)
23
+ {
24
+ return new PipelinerSales_Repository_Rest_RestRepository(
25
+ $this->baseUrl,
26
+ $entitySingular,
27
+ $entityPlural,
28
+ $this->httpClient,
29
+ $this->dateTimeFormat
30
+ );
31
+ }
32
+ }
package.xml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package><name>opswaypipeliner</name><version>1.0.0</version><stability>stable</stability><license>GPL</license><channel>community</channel><extends></extends><summary>Extension allow synchronizing data bidirectional between Magento stores and PipelinerCRM system. </summary><description>Why Pipeliner CRM? Because Seeing Is Believing
3
+ &lt;br /&gt;&lt;br /&gt;
4
+ Visualize your sales pipeline and gain actionable insights. Open your Pipeliner CRM and get an instantly readable graphical overview of all your opportunities in the context of your sales process. A visually rich, uncluttered model of your actual sales pipeline acts as your interface. It&#x2019;s real-time sales data, organized so you can always remain focused on revenue targets.&lt;br /&gt;&lt;br /&gt;
5
+ Highlights:&lt;br /&gt;
6
+
7
+ Gain insights fast, and devise more effective sales strategies&lt;br /&gt;
8
+ Focus on proactive coaching support (instead of jumping in as a &#x201C;super closer&#x201D;)&lt;br /&gt;
9
+ Click and voila!&#x2014;Instantly &#x201C;readable&#x201D; sales and activity reports&lt;br /&gt;
10
+ Promote best practices with popular and easily understood visual tools&lt;br /&gt;
11
+ Search less and sell more&#x2014;Customer and account details are in one comprehensive view&lt;br /&gt;&lt;br /&gt;
12
+
13
+ Because Productivity Soars 24/7 (Online or Off)&lt;br /&gt;&lt;br /&gt;
14
+
15
+ Your Pipeliner CRM system is available anytime, anywhere in the world. We use a unique hybrid approach&#x2014;a blend of Cloud-based and on-premise engineering&#x2014;we call it the SMART Cloud. Your data is always safe in the Cloud, but you are always able to work with your entire system anywhere.&lt;br /&gt;&lt;br /&gt;
16
+ Highlights:
17
+ &lt;br /&gt;
18
+ Work on your own schedule, online or offline&lt;br /&gt;
19
+ Most recent synced version of your system is always ready&lt;br /&gt;
20
+ Control your schedule when on the road, at &#x201C;no access&#x201D; customer locations, on a hike, or on a plane&lt;br /&gt;&lt;br /&gt;
21
+
22
+ Smart Cloud CRM&lt;br /&gt;
23
+ Because You Can Track Progress in Real Time
24
+ &lt;br /&gt;&lt;br /&gt;
25
+ Pipeliner CRM for sales teams makes it easier to visualize and follow your sales process.&lt;br /&gt;
26
+ Highlights:&lt;br /&gt;&lt;br /&gt;
27
+
28
+ 1-to-Many-With-Any Architecture associates one Contact to Multiple Accounts, providing context for your complex and interconnected relationships, as they evolve.
29
+ Full-Featured Product Catalog is a massive time saver. Customers can apply discounts, taxes, and automatically calculate fields -- tying the sales process more intimately to workflow.&lt;br /&gt;&lt;br /&gt;
30
+ Forecasts are front and center, updated in real time&lt;br /&gt;
31
+ View pipeline from any angle at any moment&lt;br /&gt;
32
+ Notifications let users and admins set email notifications across most every Activity stream, facilitating better cross-company awareness to what matters.&lt;br /&gt;
33
+ Our Advanced Calendar syncs with Google and Outlook! There&#x2019;s also an agenda view, activity extract, and support for 5 different layouts.&lt;br /&gt;
34
+ Save your favorite views as Profiles and see them with a click&lt;br /&gt;
35
+ Alerts, timeline views, and powerful filters improve sales forecast accuracy and lower risk&lt;br /&gt;
36
+ Collaboration tools for sharing, delegating, and interacting keep salespeople working toward goals in a timely way&lt;br /&gt;
37
+ Social monitoring adds relevance to prospect relationships and builds trust&lt;br /&gt;&lt;br /&gt;
38
+
39
+ Social CRM Feed&lt;br /&gt;
40
+ Because Common Sense Pricing Has Big Advantages
41
+ &lt;br /&gt;&lt;br /&gt;
42
+ Pipeliner is affordable, easy to implement, and has a high rate of adoption by salespeople and teams.&lt;br /&gt;&lt;br /&gt;
43
+ Highlights:&lt;br /&gt;
44
+
45
+ One low price for all the core features, plug-ins, offline client, mobile CRM app&lt;br /&gt;
46
+ Full implementation in hours&lt;br /&gt;
47
+ Full support as you come on board&lt;br /&gt;
48
+ Sales teams see value and adopt enthusiastically&lt;br /&gt;
49
+ Training in hours with low learning curve&lt;br /&gt;
50
+ Low monthly fee and no hidden costs&lt;br /&gt;
51
+ 30-day trial with no strings attached, no credit card required&lt;br /&gt;</description><notes>First release.</notes><authors><author><name>OpsWay</name><user>opsway</user><email>support@opsway.com</email></author></authors><date>2015-01-30</date><time>5:47:48</time><compatible></compatible><dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies><contents><target name="mage"><dir name="app"><dir name="code"><dir name="community"><dir name="OpsWay"><dir name="Pipeliner"><dir name="Model"><file name="Observer.php" hash="8776c533bc613b8a93f115f2a3512dac"/></dir><dir name="Block"><dir name="Adminhtml"><file name="Info.php" hash="28c5df48aa8bb0d810cecf0b2b354a38"/></dir></dir><dir name="sql"><dir name="add_category_attribute"><file name="mysql4-install-0.1.0.php" hash="3e10b5d23b9be7a8376b81e06de9f4e3"/></dir><dir name="add_product_attribute"><file name="mysql4-install-0.1.0.php" hash="ce57114b907ec036fb77966adc94dbdb"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="40c46162a55bd94e0eb89887454c074f"/><file name="config.xml" hash="490171689f34f7d8487ec8d3ace51229"/><file name="system.xml" hash="82a4d7cfa5e11e1e044938d92dc0beaf"/></dir><dir name="Helper"><file name="Data.php" hash="c823691777c829a34a854300746c9a65"/></dir></dir></dir></dir></dir><dir name="etc"><dir name="modules"><file name="Opsway_Pipeliner.xml" hash="43f8f455cec48bcbbba4f3bf57bd817b"/></dir></dir></dir><dir name="lib"><dir name="PipelinerSales"><file name="Defaults.php" hash="795a176bb235f06d3448f6b9c6c3770c"/><file name="Entity.php" hash="5522df47ab3789ac5c482153955c0846"/><file name="EntityCollection.php" hash="aa2ed3ae72b6070d80b79aca53b3a4ea"/><file name="EntityCollectionIterator.php" hash="0d9b207d7d3ec116b921d0d1caea4a0a"/><file name="InfoMethodsInterface.php" hash="f818ac50b3db96181e5de6f1fb38b5ef"/><file name="PipelinerClient.php" hash="15aec4048b43abef79bc549a850c8c21"/><file name="PipelinerClientException.php" hash="c7e0a501301d601ffc65be53fd77ba87"/><dir name="Http"><file name="CreatedResponse.php" hash="5b756f254ca570eec533b6582e9e7714"/><file name="CurlHttpClient.php" hash="bc9c2d00e16c62d37b2a9e07bab32abd"/><file name="HttpInterface.php" hash="6da80eac3ec77abd4ba380906118e755"/><file name="PipelinerHttpException.php" hash="2267d7448b8215f15db84a8ec630a17f"/><file name="Response.php" hash="945a5eb498e10c7db56792bba4def283"/></dir><dir name="Model"><file name="Version.php" hash="e7568bfd273ce4a3ee72b52d26257ed5"/></dir><dir name="Repository"><file name="RepositoryFactoryInterface.php" hash="ac965687e601d9c69124d96c299517b5"/><file name="RepositoryInterface.php" hash="65aa4dab5594b59972cd9577ae3455cb"/><dir name="Rest"><file name="RestCreatedResponse.php" hash="ddd5f97b0d4e32d192aa3f9d4e31c3d2"/><file name="RestInfoMethods.php" hash="714f1f5f0a89fec7784aac2bdaef6ef8"/><file name="RestRepository.php" hash="c21909d5078d910cb6344daae7d87187"/><file name="RestRepositoryFactory.php" hash="2862f4b0c21b21eebe8b571427372017"/></dir></dir><dir name="Query"><file name="Criteria.php" hash="2d2f2defca5480895c79352fb31dc0a3"/><file name="Filter.php" hash="b240a1be7244431fb5939a777c55c27d"/><file name="Sort.php" hash="c1ab4109a461427cd016866472989bc4"/></dir></dir></dir></target></contents></package>