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
- app/code/community/OpsWay/Pipeliner/Block/Adminhtml/Info.php +33 -0
- app/code/community/OpsWay/Pipeliner/Helper/Data.php +34 -0
- app/code/community/OpsWay/Pipeliner/Model/Observer.php +319 -0
- app/code/community/OpsWay/Pipeliner/etc/adminhtml.xml +22 -0
- app/code/community/OpsWay/Pipeliner/etc/config.xml +136 -0
- app/code/community/OpsWay/Pipeliner/etc/system.xml +68 -0
- app/code/community/OpsWay/Pipeliner/sql/add_category_attribute/mysql4-install-0.1.0.php +15 -0
- app/code/community/OpsWay/Pipeliner/sql/add_product_attribute/mysql4-install-0.1.0.php +26 -0
- app/etc/modules/Opsway_Pipeliner.xml +10 -0
- lib/PipelinerSales/Defaults.php +13 -0
- lib/PipelinerSales/Entity.php +225 -0
- lib/PipelinerSales/EntityCollection.php +156 -0
- lib/PipelinerSales/EntityCollectionIterator.php +111 -0
- lib/PipelinerSales/Http/CreatedResponse.php +20 -0
- lib/PipelinerSales/Http/CurlHttpClient.php +96 -0
- lib/PipelinerSales/Http/HttpInterface.php +34 -0
- lib/PipelinerSales/Http/PipelinerHttpException.php +72 -0
- lib/PipelinerSales/Http/Response.php +78 -0
- lib/PipelinerSales/InfoMethodsInterface.php +61 -0
- lib/PipelinerSales/Model/Version.php +101 -0
- lib/PipelinerSales/PipelinerClient.php +189 -0
- lib/PipelinerSales/PipelinerClientException.php +12 -0
- lib/PipelinerSales/Query/Criteria.php +308 -0
- lib/PipelinerSales/Query/Filter.php +132 -0
- lib/PipelinerSales/Query/Sort.php +78 -0
- lib/PipelinerSales/Repository/RepositoryFactoryInterface.php +17 -0
- lib/PipelinerSales/Repository/RepositoryInterface.php +140 -0
- lib/PipelinerSales/Repository/Rest/RestCreatedResponse.php +34 -0
- lib/PipelinerSales/Repository/Rest/RestInfoMethods.php +66 -0
- lib/PipelinerSales/Repository/Rest/RestRepository.php +229 -0
- lib/PipelinerSales/Repository/Rest/RestRepositoryFactory.php +32 -0
- 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 (|) 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 (|) 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 |
+
<br /><br />
|
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’s real-time sales data, organized so you can always remain focused on revenue targets.<br /><br />
|
5 |
+
Highlights:<br />
|
6 |
+
|
7 |
+
Gain insights fast, and devise more effective sales strategies<br />
|
8 |
+
Focus on proactive coaching support (instead of jumping in as a “super closer”)<br />
|
9 |
+
Click and voila!—Instantly “readable” sales and activity reports<br />
|
10 |
+
Promote best practices with popular and easily understood visual tools<br />
|
11 |
+
Search less and sell more—Customer and account details are in one comprehensive view<br /><br />
|
12 |
+
|
13 |
+
Because Productivity Soars 24/7 (Online or Off)<br /><br />
|
14 |
+
|
15 |
+
Your Pipeliner CRM system is available anytime, anywhere in the world. We use a unique hybrid approach—a blend of Cloud-based and on-premise engineering—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.<br /><br />
|
16 |
+
Highlights:
|
17 |
+
<br />
|
18 |
+
Work on your own schedule, online or offline<br />
|
19 |
+
Most recent synced version of your system is always ready<br />
|
20 |
+
Control your schedule when on the road, at “no access” customer locations, on a hike, or on a plane<br /><br />
|
21 |
+
|
22 |
+
Smart Cloud CRM<br />
|
23 |
+
Because You Can Track Progress in Real Time
|
24 |
+
<br /><br />
|
25 |
+
Pipeliner CRM for sales teams makes it easier to visualize and follow your sales process.<br />
|
26 |
+
Highlights:<br /><br />
|
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.<br /><br />
|
30 |
+
Forecasts are front and center, updated in real time<br />
|
31 |
+
View pipeline from any angle at any moment<br />
|
32 |
+
Notifications let users and admins set email notifications across most every Activity stream, facilitating better cross-company awareness to what matters.<br />
|
33 |
+
Our Advanced Calendar syncs with Google and Outlook! There’s also an agenda view, activity extract, and support for 5 different layouts.<br />
|
34 |
+
Save your favorite views as Profiles and see them with a click<br />
|
35 |
+
Alerts, timeline views, and powerful filters improve sales forecast accuracy and lower risk<br />
|
36 |
+
Collaboration tools for sharing, delegating, and interacting keep salespeople working toward goals in a timely way<br />
|
37 |
+
Social monitoring adds relevance to prospect relationships and builds trust<br /><br />
|
38 |
+
|
39 |
+
Social CRM Feed<br />
|
40 |
+
Because Common Sense Pricing Has Big Advantages
|
41 |
+
<br /><br />
|
42 |
+
Pipeliner is affordable, easy to implement, and has a high rate of adoption by salespeople and teams.<br /><br />
|
43 |
+
Highlights:<br />
|
44 |
+
|
45 |
+
One low price for all the core features, plug-ins, offline client, mobile CRM app<br />
|
46 |
+
Full implementation in hours<br />
|
47 |
+
Full support as you come on board<br />
|
48 |
+
Sales teams see value and adopt enthusiastically<br />
|
49 |
+
Training in hours with low learning curve<br />
|
50 |
+
Low monthly fee and no hidden costs<br />
|
51 |
+
30-day trial with no strings attached, no credit card required<br /></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>
|