Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • ActionMetaData
  • ActionText
  • Admin
  • AmorphousModel
  • ApiHook
  • APIModel
  • ChartSetting
  • ContactForm
  • ContactList
  • Credentials
  • Criteria
  • Dropdowns
  • Events
  • EventsData
  • Fields
  • FormLayout
  • Imports
  • InlineEmail
  • LeadRouting
  • Locations
  • LoginForm
  • Maps
  • Modules
  • Notes
  • Notification
  • PhoneNumber
  • Profile
  • Record
  • Relationships
  • Roles
  • RoleToPermission
  • RoleToUser
  • RoleToWorkflow
  • Rules
  • Session
  • SessionLog
  • Social
  • Tags
  • TempFile
  • Tips
  • Tours
  • TrackEmail
  • TriggerLog
  • URL
  • ViewLog
  • Widgets
  • X2List
  • X2ListCriterion
  • X2ListItem
  • X2Model
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: 
  3: /*****************************************************************************************
  4:  * X2Engine Open Source Edition is a customer relationship management program developed by
  5:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
  6:  * 
  7:  * This program is free software; you can redistribute it and/or modify it under
  8:  * the terms of the GNU Affero General Public License version 3 as published by the
  9:  * Free Software Foundation with the addition of the following permission added
 10:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 11:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
 12:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 13:  * 
 14:  * This program is distributed in the hope that it will be useful, but WITHOUT
 15:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 16:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 17:  * details.
 18:  * 
 19:  * You should have received a copy of the GNU Affero General Public License along with
 20:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
 21:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 22:  * 02110-1301 USA.
 23:  * 
 24:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
 25:  * California 95067, USA. or at email address contact@x2engine.com.
 26:  * 
 27:  * The interactive user interfaces in modified source and object code versions
 28:  * of this program must display Appropriate Legal Notices, as required under
 29:  * Section 5 of the GNU Affero General Public License version 3.
 30:  * 
 31:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 32:  * these Appropriate Legal Notices must retain the display of the "Powered by
 33:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
 34:  * technical reasons, the Appropriate Legal Notices must display the words
 35:  * "Powered by X2Engine".
 36:  *****************************************************************************************/
 37: 
 38: /**
 39:  * Handles External API subscriptions (for pushing data).
 40:  *
 41:  * This model record is for managing "subscriptions" to events in X2Engine. The
 42:  * purpose behind this all is to allow a means to enact changes to other systems
 43:  * in response to events in X2Engine without the need for polling.
 44:  *
 45:  * Each time {@link X2Flow::trigger} is called, all hooks matching the name of
 46:  * the triggering event will also be called. Then, POST requests will be sent to
 47:  * the URL specified by the "target_url" attribute, and the payload will be
 48:  * either arbitrary data, or a URL within the REST API at which to retrieve the
 49:  * payload (if the payload is an instance of {@link X2Model}). Each record of
 50:  * this model type is thus in effect a request to either send data to a remote
 51:  * service, or notify that remote service that it needs to fetch data from
 52:  * X2Engine at a given resource location.
 53:  *
 54:  * @package application.models
 55:  * @author Demitri Morgan <demitri@x2engine.com>
 56:  */
 57: class ApiHook extends CActiveRecord {
 58: 
 59:     /**
 60:      * No more than this number of remote hooks can run at a time.
 61:      */
 62:     const MAX_API_HOOK_BATCH = 3;
 63: 
 64:     /**
 65:      * Hook timeout after 3 seconds
 66:      */
 67:     const MAX_WAIT_TIME = 3;
 68: 
 69:     /**
 70:      * Response data, or false if no requests have yet been sent.
 71:      * @var mixed
 72:      */
 73:     public $sent = false;
 74:     
 75:     /**
 76:      * Most-recently-created CURL session handle
 77:      * @var type 
 78:      */
 79:     private $_ch;
 80: 
 81:     public function attributeLabels() {
 82:         return array(
 83:             'id' => Yii::t('app','ID'),
 84:             'target_url' => Yii::t('app','Target URL'),
 85:             'event' => Yii::t('app','Event')
 86:         );
 87:     }
 88: 
 89:     /**
 90:      * Sends a deletion request to the "subscription" URL
 91:      */
 92:     public function beforeDelete(){
 93:         if($this->scenario != 'delete.remote')
 94:             $this->send('DELETE');
 95:         return parent::beforeDelete();
 96:     }
 97: 
 98:     /**
 99:      * Composes and returns a {@link CDbCriteria}-compatible property array for
100:      * querying hooks for any given event.
101:      * 
102:      * Creates the criteria properties for fetching all hooks for a specified 
103:      * model name, event name and user ID.
104:      *
105:      * @param string $event Event name
106:      * @param string $modelName Model name associated with the hook; used for
107:      *  distinguishing generic events such as "a record was created"
108:      * @param integer $userId run hooks for a user with this ID.
109:      * @return array
110:      */
111:     public static function criteria($event,$modelName,$userId) {
112:         $criteria = array(
113:             'condition' => "`t`.`event`=:event "
114:                 . "AND `t`.`userId`".($userId != X2_PRIMARY_ADMIN_ID
115:                     ? " IN (".X2_PRIMARY_ADMIN_ID.",:userId)" // User hooks + system hooks
116:                     : "=:userId"), // Admin's hooks (also apply system-wide)
117:             'params' => array(
118:                 ':event' => $event,
119:                 ':userId' => $userId
120:             ),
121:             'alias' => 't'
122:         );
123:         if(!empty($modelName)) {
124:             $criteria['condition'] .= ' AND `t`.`modelName`=:modelName';
125:             $criteria['params'][':modelName'] = $modelName;
126:         }
127:         return $criteria;
128:     }
129: 
130:     /**
131:      * Getter w/stripped code (Platinum-only settings) for the maximum number of
132:      * API hooks to send.
133:      *
134:      * @return type
135:      */
136:     public function getMaxNHooks() {
137:         $max = self::MAX_API_HOOK_BATCH;
138:         
139:         return $max;
140:     }
141: 
142:     /**
143:      * Returns the last status code
144:      */
145:     public function getStatus() {
146:         if(isset($this->_ch)) {
147:             return curl_getinfo($this->_ch,CURLINFO_HTTP_CODE);
148:         }
149:         return 0;
150:     }
151: 
152:     public function getTimeout() {
153:         $timeout = self::MAX_WAIT_TIME;
154:         
155:         return $timeout;
156:     }
157: 
158:     public function insert($attributes = null){
159:         $this->createDate = time();
160:         return parent::insert($attributes);
161:     }
162:     
163:     /**
164:      * Validator for limiting the number of hooks on a given action.
165:      *
166:      * @param type $attribute
167:      * @param type $params
168:      */
169:     public function maxBatchSize($attribute,$params=array()) {
170:         $max = $this->getMaxNHooks();
171:         $params = compact($attribute);
172:         $criteria = self::criteria($this->$attribute, $this->modelName,
173:                 $this->userId);
174:         if(self::model()->count($criteria)>=$max) {
175:             $this->addError($attribute,Yii::t('app','The maximum number of '
176:                     . 'hooks ({n}) has been reached for events of this type.',
177:                     array('{n}'=>$max)));
178:         }
179:     }
180: 
181:     public static function model($className = __CLASS__){
182:         return parent::model($className);
183:     }
184: 
185:     public function rules() {
186:         return array(
187:             array('event','maxBatchSize'),
188:             array('directPayload','boolean','allowEmpty'=>true),
189:             array('target_url','required')
190:         );
191:     }
192: 
193:     /**
194:      * Runs the API hook; sends data to the third-party service.
195:      *
196:      * @param X2Model $output The relevant model object
197:      * @return \ApiHook
198:      */
199:     public function run($output) {
200:         $this->send('POST',$output);
201:         return $this;
202:     }
203: 
204:     /**
205:      * Runs all API hooks corresponding to an event, a model (or arbitrary
206:      * payload), and a user.
207:      *
208:      * @param string $event Name of the event
209:      * @param mixed $output The relevant model object
210:      * @param integer $userId the ID of the acting user in running the API hook
211:      */
212:     public static function runAll($event,$output=null,$userId=null) {
213:         $modelName = (isset($output['model']) && $output['model'] instanceof X2Model)
214:                 ? get_class($output['model'])
215:                 : null;
216:         $userId = $userId === null
217:                 ? Yii::app()->getSuId()
218:                 : $userId;
219: 
220:         // Cache the current query and use the cached value if the number of
221:         // records and last updated time haven't changed
222:         $cacheDep = new CDbCacheDependency('SELECT MAX(`createDate`),COUNT(*) '
223:                 . 'FROM `'.self::model()->tableName().'`');
224:         $hookCriteria = self::criteria($event, $modelName, $userId);
225:         return array_map(function($h)use($output){
226:             return $h->run($output);
227:         },self::model()->cache(86400,$cacheDep)->findAll($hookCriteria));
228:     }
229: 
230:     /**
231:      * Sends a request to pull data from X2Engine, or to delete/unsubscribe.
232:      *
233:      * @param string $method Request method to use
234:      * @param array $data an array to JSON-encode and send
235:      */
236:     public function send($method,$data) {
237:         if(!extension_loaded('curl'))
238:             return;
239:         // Compose the body of the request to send
240:         $payload = json_encode($this->walkData($data));
241: 
242:         // Start a cURL session and configure the request
243:         $this->_ch = curl_init($this->target_url);
244:         curl_setopt_array($this->_ch,array(
245:             CURLOPT_CUSTOMREQUEST => $method,
246:             CURLOPT_RETURNTRANSFER => true,
247:             CURLOPT_TIMEOUT => $this->getTimeout(),
248:             CURLOPT_HTTPHEADER => array('Content-Type: application/json; charset=utf-8'),
249:             CURLOPT_HTTP200ALIASES => array_keys(ResponseUtil::getStatusMessages()),
250:         ));
251:         if(!empty($payload))
252:             curl_setopt($this->_ch,CURLOPT_POSTFIELDS,$payload);
253: 
254:         // Send the request
255:         $this->sent = curl_exec($this->_ch);
256: 
257:         // If the remote end is no longer listening, we can stop sending data
258:         if($this->getStatus() == 410) {
259:             $this->setScenario('delete.remote');
260:             $this->delete();
261:         }
262:     }
263: 
264:     public function tableName() {
265:         return 'x2_api_hooks';
266:     }
267: 
268:     /**
269:      * Recursively walk a payload variable, converting it appropriately to an
270:      * array so that it can be JSON-encoded.
271:      *
272:      * @param type $payload The data to be converted to an array.
273:      * @param type $key An array key at any level if the recursion depth is
274:      *  nonzero; null otherwise.
275:      * @return array If the key is not null, it will return a 2-element array
276:      *  with its first element being the key (or a different one) and the second
277:      *  the converted value; $key === null implies the first level of recursion
278:      */
279:     private function walkData($payload,$key=null){
280:         switch(gettype($payload)){
281:             case 'array':
282:                 $output = array();
283:                 foreach($payload as $nestedKey=>$value) {
284:                     list($outKey,$outValue) = $this->walkData($value,$nestedKey);
285:                     $output[$outKey] = $outValue;
286:                 }
287:                 return $key === null
288:                         ? $output // Top level of recursion
289:                         : array($key,$output); // A nested array at key $key
290:             case 'object':
291:                 if($payload instanceof CActiveRecord){
292:                     // Send a resource URL for the remote end to retrieve, if
293:                     // if it's X2Model we're working with, or direct payload is
294:                     // disabled. Otherwise, send the attributes directly.
295:                     $direct = $this->directPayload || !($payload instanceof X2Model);
296:                     $extraFields = $payload instanceof Actions
297:                             ? array('actionDescription'=>$payload->getActionDescription())
298:                             : array();
299:                     return array(
300:                         $direct ? $key : 'resource_url',
301:                         $direct ? array_merge($payload->attributes,$extraFields)
302:                                 : Yii::app()->createExternalUrl('/api2/model', array(
303:                                     '_class' => get_class($payload),
304:                                     '_id' => $payload->id
305:                     )));
306:                 } else {
307:                     return array($key,'Object');
308:                 }
309:             case 'resource':
310:                 return array($key,'Resource');
311:             default:
312:                 return array($key,$payload);
313:         }
314:     }
315: 
316: }
317: 
318: ?>
319: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0