1: <?php
2:
3: /**
4: * Class QJsClosure
5: *
6: * An object which represents a javascript closure (annonymous function). Use this to embed a
7: * function into a PHP array or object that eventually will get turned into javascript.
8: */
9: class QJsClosure implements JsonSerializable {
10: /** @var string The js code for the function. */
11: protected $strBody;
12: /** @var array parameter names for the function call that get passed into the function. */
13: protected $strParamsArray;
14:
15: /**
16: * @param string $strBody The function body
17: * @param array|null $strParamsArray The names of the parameters passed in the function call
18: */
19: public function __construct($strBody, $strParamsArray = null) {
20: $this->strBody = $strBody;
21: $this->strParamsArray = $strParamsArray;
22: }
23:
24: /**
25: * Return a javascript enclosure. Enclosures cannot be included in JSON, so we need to create a custom
26: * encoding to include in the json that will get decoded at the other side.
27: *
28: * @return string
29: */
30: public function toJsObject() {
31: $strParams = $this->strParamsArray ? implode(', ', $this->strParamsArray) : '';
32: return 'function('.$strParams.') {' . $this->strBody . '}';
33: }
34:
35: /**
36: * Converts the object into something serializable by json_encode. Will get decoded in qcubed.unpackObj
37: * @return mixed
38: */
39: public function jsonSerialize() {
40: // Encode in a way to decode in qcubed.js
41: $a[JavaScriptHelper::ObjectType] = 'qClosure';
42: $a['func'] = $this->strBody;
43: $a['params'] = $this->strParamsArray;
44: return JavaScriptHelper::MakeJsonEncodable($a);
45: }
46: }
47:
48: /**
49: * Wrapper class for arrays to control whether the key in the array is quoted.
50: * In some situations, a quoted key has a different meaning from a non-quoted key.
51: * For example, when making a list of parameters to pass when calling the jQuery $() command,
52: * (i.e. $j(selector, params)), quoted words are turned into parameters, and non-quoted words
53: * are turned into functions. For example, "size" will set the size attribute of the object, and
54: * size (no quotes), will call the size() function on the object.
55: *
56: * To use it, simply wrap the value part of the array with this class.
57: * @usage: $a = array ("click", new QJsNoQuoteKey (new QJsClosure('alert ("I was clicked")')));
58: */
59: class QJsNoQuoteKey implements JsonSerializable {
60: protected $mixContent;
61:
62: public function __construct ($mixContent) {
63: $this->mixContent = $mixContent;
64: }
65:
66: public function toJsObject() {
67: return JavaScriptHelper::toJsObject($this->mixContent);
68: }
69:
70: public function jsonSerialize() {
71: return $this->mixContent;
72: }
73:
74: }
75:
76: /**
77: * Class QJsVarName
78: * Outputs a string without quotes to specify a global variable name. Strings are normally quoted. Dot notation
79: * can be used to specify items within globals.
80: */
81: class QJsVarName implements JsonSerializable {
82: protected $strContent;
83:
84: public function __construct($strContent) {
85: $this->strContent = $strContent;
86: }
87:
88: public function toJsObject() {
89: return $this->strContent;
90: }
91:
92: public function jsonSerialize() {
93: $a[JavaScriptHelper::ObjectType] = 'qVarName';
94: $a['varName'] = $this->strContent;
95: return JavaScriptHelper::MakeJsonEncodable($a);
96: }
97: }
98:
99: /**
100: * Class QJsFunction
101: * Outputs a function call to a global function or function in an object referenced from global space. The purpose
102: * of this is to immediately use the results of the function call, as opposed to a closure, which stores a pointer
103: * to a function that is used later.
104: */
105: class QJsFunction implements JsonSerializable {
106: /** @var string|null */
107: protected $strContext;
108: /** @var string */
109: protected $strFunctionName;
110: /** @var array|null */
111: protected $params;
112:
113: /**
114: * QJsFunction constructor.
115: * @param string $strFunctionName The name of the function call.
116: * @param null|array $strParamsArray If given, the parameters to send to the function call
117: * @param null $strContext If given, the object in the window object which contains the function and is the context for the function.
118: * Use dot '.' notation to traverse the object tree. i.e. "obj1.obj2" refers to window.obj1.obj2 in javascript.
119: */
120: public function __construct($strFunctionName, $params = null, $strContext = null) {
121: $this->strFunctionName = $strFunctionName;
122: $this->params = $params;
123: $this->strContext = $strContext;
124: }
125:
126: /**
127: * Returns this as a javascript string to be included in the end script of the page.
128: * @return string
129: */
130: public function toJsObject() {
131: if ($this->params) {
132: foreach ($this->params as $param) {
133: $strParams[] = JavaScriptHelper::toJsObject($param);
134: }
135: $strParams = implode (",", $strParams);
136: }
137: else {
138: $strParams = '';
139: }
140: $strFuncName = $this->strFunctionName;
141: if ($this->strContext) {
142: $strFuncName = $this->strContext . '.' . $strFuncName;
143: }
144: return $strFuncName . '('.$strParams.')';
145: }
146:
147: /**
148: * Returns this as a json object to be sent to qcubed.js during ajax drawing.
149: * @return mixed
150: */
151: public function jsonSerialize() {
152: $a[JavaScriptHelper::ObjectType] = 'qFunc';
153: $a['func'] = $this->strFunctionName;
154: if ($this->strContext) {
155: $a['context'] = $this->strContext;
156: }
157: if ($this->params) {
158: $a['params'] = $this->params;
159: }
160:
161: return JavaScriptHelper::MakeJsonEncodable($a);
162: }
163: }
164:
165:
166:
167: /**
168: * Class QJsonParameterList
169: * A Wrapper class that will render an array without the brackets, so that it becomes a variable length parameter list.
170: */
171: class QJsParameterList {
172: protected $arrContent;
173:
174: public function __construct ($arrContent) {
175: $this->arrContent = $arrContent;
176: }
177:
178: public function toJsObject() {
179: $strList = '';
180: foreach ($this->arrContent as $objItem) {
181: if (strlen($strList) > 0) $strList .= ',';
182: $strList .= JavaScriptHelper::toJsObject($objItem);
183: }
184: return $strList;
185: }
186: }
187:
188: /**
189: * A wrapper class for generating an ajax action with no script. This is helpful in situations where data
190: * will be returned to the javascript object later.
191: *
192: * Class QNoScriptAjaxAction
193: */
194: class QNoScriptAjaxAction extends QAjaxAction {
195: private $objTargetAction;
196:
197: function __construct(QAction $objTargetAction) {
198: $this->objTargetAction = $objTargetAction;
199: }
200:
201: public function __get($strName) {
202: if ($strName == 'Event')
203: return parent::__get($strName);
204: return $this->objTargetAction->__get($strName);
205: }
206:
207: public function RenderScript(QControl $objControl) {
208: return '';
209: }
210: }
211:
212: /**
213: * Class JavaScriptHelper: used to help with generating javascript code
214: */
215: abstract class JavaScriptHelper {
216:
217: const ObjectType = 'qObjType'; // Identifies a JSON object as an object we want handle specially in qcubed.js
218: /**
219: * Returns javascript that on execution will insert the value $strValue into the DOM element corresponding to
220: * the $objControl using the key $strKey
221: *
222: * @static
223: * @param QControl $objControl
224: * @param string $strKey
225: * @param string $strValue any javascript variable or object
226: * @return string data insertion javascript
227: */
228: public static function customDataInsertion(QControl $objControl, $strKey, $strValue) {
229: return 'jQuery("#'.$objControl->ControlId.'").data("'.$strKey.'", '.$strValue.');';
230: }
231:
232: /**
233: * Returns javascript that on execution will retrieve the value from the DOM element corresponding to
234: * the $objControl using the key $strKey and assign it to the javascript variable $strValue.
235: *
236: * @static
237: * @param QControl $objControl
238: * @param string $strKey
239: * @param string $strValue
240: * @return string data retrieval javascript
241: */
242: public static function customDataRetrieval(QControl $objControl, $strKey, $strValue) {
243: return 'var '.$strValue.' = jQuery("#'.$objControl->ControlId.'").data("'.$strKey.'");';
244: }
245:
246: /**
247: * Helper class to convert a name from camel case to using dashes to separated words.
248: * data-* html attributes have special conversion rules. Key names should always be lower case. Dashes in the
249: * name get converted to camel case javascript variable names by jQuery.
250: * For example, if you want to pass the value with key name "testVar" from PHP to javascript by printing it in
251: * the html, you would use this function to help convert it to "data-test-var", after which you can retrieve
252: * in in javascript by calling ".data('testVar')". on the object.
253: * @param $strName
254: * @return string
255: * @throws QCallerException
256: */
257: public static function dataNameFromCamelCase($strName) {
258: if (preg_match('/[A-Z][A-Z]/', $strName)) {
259: throw new QCallerException ('Not a camel case string');
260: }
261: return preg_replace_callback('/([A-Z])/',
262: function ($matches) {
263: return '-' . strtolower($matches[1]);
264: },
265: $strName
266: );
267:
268: }
269:
270: /**
271: * Converts an html data attribute name to camelCase.
272: *
273: * @param $strName
274: * @return string
275: */
276: public static function dataNameToCamelCase($strName) {
277: return preg_replace_callback('/-([a-z])/',
278: function ($matches) {
279: return ucfirst($matches[1]);
280: },
281: $strName
282: );
283: }
284:
285: public static function jsEncodeString($objValue) {
286: // default to string if not specified
287: static $search = array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"');
288: static $replace = array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
289: return '"' . str_replace($search, $replace, $objValue) . '"';
290: }
291:
292: /**
293: * Recursively convert a php object to a javascript object.
294: * If the $objValue is an object other than Date and has a toJsObject() method, the method will be called
295: * to perform the conversion. Array values are recursively converted as well.
296: *
297: * This string is designed to create the object if it was directly output to the browser. See toJSON below
298: * for an equivalent version that is passable through a json interface.
299: *
300: * @static
301: * @param mixed $objValue the php object to convert
302: * @return string javascript representation of the php object
303: */
304: public static function toJsObject($objValue) {
305: $strRet = '';
306:
307: switch (gettype($objValue)) {
308: case 'double':
309: case 'integer':
310: $strRet = (string)$objValue;
311: break;
312:
313: case 'boolean':
314: $strRet = $objValue? 'true' : 'false';
315: break;
316:
317: case 'string':
318: $strRet = self::jsEncodeString($objValue);
319: break;
320:
321: case 'NULL':
322: $strRet = 'null';
323: break;
324:
325: case 'object':
326: if (method_exists($objValue, 'toJsObject')) {
327: $strRet = $objValue->toJsObject();
328: }
329: break;
330:
331: case 'array':
332: $array = (array)$objValue;
333: if (0 !== count(array_diff_key($array, array_keys(array_keys($array))))) {
334: // associative array - create a hash
335: $strHash = '';
336: foreach ($array as $objKey => $objItem) {
337: if ($strHash) $strHash .= ',';
338: if ($objItem instanceof QJsNoQuoteKey) {
339: $strHash .= $objKey.': '.self::toJsObject($objItem);
340: } else {
341: $strHash .= self::toJsObject($objKey).': '.self::toJsObject($objItem);
342: }
343: }
344: $strRet = '{'.$strHash.'}';
345: }
346: else {
347: // simple array - create a list
348: $strList = '';
349: foreach ($array as $objItem) {
350: if (strlen($strList) > 0) $strList .= ',';
351: $strList .= self::toJsObject($objItem);
352: }
353: $strRet = '['.$strList.']';
354: }
355:
356: break;
357:
358: default:
359: $strRet = self::jsEncodeString((string)$objValue);
360: break;
361:
362: }
363: return $strRet;
364: }
365:
366: /**
367: * Our specialized json encoder. Strings will be converted to UTF-8. Arrays will be recursively searched and
368: * both keys and values made UTF-8. Objects will be converted with json_encode, and so objects that need a special
369: * encoding should implement the jsonSerializable interface. See below
370: * @param mixed $objValue
371: * @return string
372: */
373: public static function toJSON($objValue) {
374: assert ('is_array($objValue) || is_object($objValue)'); // json spec says only arrays or objects can be encoded
375: $objValue = JavaScriptHelper::MakeJsonEncodable($objValue);
376: $strRet = json_encode($objValue);
377: if ($strRet === false) {
378: throw new QCallerException ('Json Encoding Error: ' . json_last_error_msg());
379: }
380: return $strRet;
381: }
382:
383: /**
384: * Convert an object to a structure that we can call json_encode on. This is particularly meant for the purpose of
385: * sending json data to qcubed.js through ajax, but can be used for other things as well.
386: *
387: * PHP 5.4 has a new jsonSerializable interface that objects should use to modify their encoding if needed. Otherwise,
388: * public member variables will be encoded. The goal of object serialization should be to be able to send it
389: * to qcubed.unpackParams in qcubed.js to create the javascript form of the object. This decoder will look for objects
390: * that have the 'qObjType' key set and send the object to the special unpacker.
391: *
392: * DateTime handling is absent below. DateTime objects will get converted, but not in a very useful way. If you
393: * are using strict DateTime objects (not likely since the framework normally uses QDateTime for all date objects),
394: * you should convert them to QDateTime objects before sending them here.
395: *
396: * @param mixed $objValue
397: * @return mixed
398: */
399: public static function MakeJsonEncodable($objValue) {
400: if (QApplication::$EncodingType && QApplication::$EncodingType == 'UTF-8') {
401: return $objValue; // Nothing to do, since all strings are already UTF-8 and objects can take care of themselves.
402: }
403:
404: switch (gettype($objValue)) {
405: case 'string':
406: $objValue = mb_convert_encoding($objValue, 'UTF-8', QApplication::$EncodingType);
407: return $objValue;
408:
409: case 'array':
410: $newArray = array();
411: foreach ($objValue as $key=>$val) {
412: $key = self::makeJsonEncodable($key);
413: $val = self::makeJsonEncodable($val);
414: $newArray[$key] = $val;
415: }
416: return $newArray;
417:
418: default:
419: return $objValue;
420:
421: }
422: }
423:
424: /**
425: * Utility function to make sure a script is terminated with a semicolon.
426: *
427: * @param $strScript
428: * @return string
429: */
430: public static function TerminateScript($strScript) {
431: if (!$strScript) return '';
432: if (!($strScript = trim ($strScript))) return '';
433: if (substr($strScript, -1) != ';') {
434: $strScript .= ';';
435: }
436: return $strScript . _nl();
437: }
438:
439: }
440: