1: <?php
2: /*****************************************************************************************
3: * X2Engine Open Source Edition is a customer relationship management program developed by
4: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
5: *
6: * This program is free software; you can redistribute it and/or modify it under
7: * the terms of the GNU Affero General Public License version 3 as published by the
8: * Free Software Foundation with the addition of the following permission added
9: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
11: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12: *
13: * This program is distributed in the hope that it will be useful, but WITHOUT
14: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16: * details.
17: *
18: * You should have received a copy of the GNU Affero General Public License along with
19: * this program; if not, see http://www.gnu.org/licenses or write to the Free
20: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21: * 02110-1301 USA.
22: *
23: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
24: * California 95067, USA. or at email address contact@x2engine.com.
25: *
26: * The interactive user interfaces in modified source and object code versions
27: * of this program must display Appropriate Legal Notices, as required under
28: * Section 5 of the GNU Affero General Public License version 3.
29: *
30: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31: * these Appropriate Legal Notices must retain the display of the "Powered by
32: * X2Engine" logo. If the display of the logo is not reasonably feasible for
33: * technical reasons, the Appropriate Legal Notices must display the words
34: * "Powered by X2Engine".
35: *****************************************************************************************/
36:
37: /**
38: * Model to provide one-time tips to help user learn a feature.
39: * @package application.models
40: * @author Alex Rowe <alex@x2engine>
41: */
42: class Tours extends CActiveRecord {
43:
44: /**
45: * Whethers tours JS has been registered or not
46: * @var boolean
47: */
48: private static $registerJS = true;
49:
50: /**
51: * Default params for the config array
52: * @see Tours::tip()
53: */
54: private static $defaultParams = array (
55:
56: // if set to true, will render a partial in the tour body
57: // ex. Tours::tip('application.views._myTipPartial', array('partial' => true));
58: 'partial' => false,
59:
60: // View File params to be passed to the view file. Only relevant
61: // if 'partial' is present
62: 'viewParams' => array(),
63:
64: // Type of tip to determine tip class
65: 'type' => 'flash',
66:
67: // If set to a string, content will be translated before being rendered.
68: 'translate' => false,
69:
70: // Extra Html options to be merged in
71: 'htmlOptions' => array(),
72:
73:
74: // Array of string replacements for easy interpolation
75: 'replace' => array(),
76:
77: // Array of string replacements for easy interpolation
78: 'title' => '',
79:
80: // Optional key for the tip. Default is a md5 hash of the content
81: 'key' => null,
82:
83: // To revise a tip, put the new content in the revision key to retain the
84: // Tip being seen on installations.
85: 'revision' => '',
86:
87: // --- PopUp Type Specific Keys ---
88:
89: // Wether or not to draw a border around the target element
90: 'highlight' => false,
91:
92: // Target element to attach the tip to. Fed to JS as a selector.
93: // ex. #edit-profile-button
94: 'target' => null,
95: );
96:
97: /**
98: * @return string the associated database table name
99: */
100: public function tableName () {
101: return 'x2_tours';
102: }
103:
104: public static function model ($className='Tours'){
105: return parent::model($className);
106: }
107:
108: /**
109: * Main API to create a tip.
110: *
111: * EXAMPLES:
112: *---------------------------------------------------
113: * 1.This will display as a flash-style tip with the specified text
114: * Tours::tip (array(
115: * 'content' => "<h1>Welcome to the profile page</h1>"
116: * ));
117: *
118: *---------------------------------------------------
119: * 2. This example will create a Q-tip dialog,
120: * and highlight the element '#create-new-user-button'.
121: * Tours::tip ( array(
122: * 'content' => 'The button here will create a new user",
123: * 'target' => '#create-new-user-button'
124: * ));
125: *
126: *
127: * @param string $content HTML, text, or partial alias OR array of tips
128: * @param array $params Config array (Described below)
129: * @return string HTML for a tip
130: */
131: public static function tip ($params=array(), $return = false) {
132: // $params can be a string for an simple tip
133: if (is_string($params)) {
134: return self::tip (array('content' => $params), $return);
135: }
136:
137: // If content is not set, no tip can be created
138: if (!isset($params['content'])) {
139: throw new Exception("Tips must include a 'content' key");
140: }
141:
142: // Dont show tips if user has tips off
143: if (!Yii::app()->params->profile->showTours) {
144: return;
145: }
146:
147: // Register JS if it is the first tip rendered
148: if (Tours::$registerJS) {
149: Yii::app()->clientScript->registerPackage('tours');
150: Tours::$registerJS = false;
151: }
152:
153: // Merge Paramters with default
154: $params = array_merge (self::$defaultParams, $params);
155:
156: $content = $params['content'];
157:
158: // By default the key is an md5 of the content
159: if ($params['key']) {
160: $key = $params['key'];
161: } else {
162: $key = md5($content);
163: }
164:
165: // Get a tip if it hasn't been seen
166: $tour = self::getTip ($key);
167: if(!$tour) {
168: return;
169: }
170:
171: if ($params['revision']) {
172: $content = $params['revision'];
173: }
174:
175: // Merge Html Options, Default class string
176: $htmlOptions = array_merge (array(
177: 'class' => '',
178: ), $params['htmlOptions']);
179:
180:
181: // Translate if specified
182: if($params['translate']) {
183: $content = Yii::t($params['translate'], $content);
184: }
185:
186: // set content to partial if specified
187: if ($params['partial']) {
188: $content = Yii::app()->controller->renderPartial (
189: $content, $params['viewParams'], true);
190: }
191:
192: // Replace all replacements
193: foreach ($params['replace'] as $key => $value) {
194: $content = preg_replace("/$key/", $value, $content);
195: }
196:
197: // --- Popup specifics --
198: // If target is set, create a popup classed tour
199: if ($params['target']) {
200: $params['type'] = 'popup';
201: $htmlOptions['data-target'] = $params['target'];
202: }
203:
204: // set content to partial if specified
205: if ($params['highlight']) {
206: $htmlOptions['data-highlight'] = true;
207: }
208:
209: // Set the type of tip as a class
210: $htmlOptions['class'] .= " $params[type]";
211:
212: // Return rendered partial
213: $html = self::render($tour, $content, $params, $htmlOptions);
214:
215: if ($return) {
216: return $html;
217: } else {
218: echo $html;
219: }
220: }
221:
222: /**
223: * renders an array of tips
224: * @param array $tips array of tip arrays. @see self::tip
225: * @param boolean $return Wether or not to return html
226: * @return string empty if return = false
227: */
228: public static function tips ($tips = array(), $return = false){
229: $html = '';
230:
231: foreach ($tips as $tip) {
232: $html .= self::tip ($tip, $return);
233: }
234:
235: return $html;
236: }
237:
238: /**
239: * Renders a file specifically to view tips.
240: * It is a simple rener partial call and the partial should echo out the tips
241: * @param string $partial Partial name (in components/tours)
242: * @param boolean $return wether to return or echo the contents
243: * @return string contents if return is true
244: */
245: public static function loadTips($partial=null, $return=false) {
246: if (!$partial) {
247: $partial = Yii::app()->controller->id.'.'.Yii::app()->controller->action->id;
248: }
249:
250: return Yii::app()->controller->renderPartial (
251: "application.views.tours.$partial", $return);
252: }
253:
254: /**
255: * Checks if a tip has been seen returns false if it has been seen
256: * @param string $key Key to find a tip for
257: * @return mixed Returns false if the tip has been seen, and returns the tip object
258: * if it has not been seen yet.
259: */
260: public static function getTip ($key) {
261: $tip = self::model('Tours')->findByAttributes (array(
262: 'profileId' => Yii::app()->params->profile->id,
263: 'description' => $key,
264: ));
265:
266:
267: if ($tip && $tip->seen) {
268: return false;
269: }
270:
271: if (empty($tip)) {
272: $tip = new Tours;
273: $tip->profileId = Yii::app()->params->profile->id;
274: $tip->description = $key;
275: $tip->save();
276: }
277:
278: return $tip;
279: }
280:
281: private static function render($tour, $content, $params, $htmlOptions = array()) {
282: return Yii::app()->controller->renderPartial ('application.views.tours.tour', array(
283: 'tour' => $tour,
284: 'title' => $params['title'],
285: 'content' => $content,
286: 'htmlOptions' => $htmlOptions
287: ), true);
288: }
289:
290:
291: }
292: