1: <?php
2: /**
3: * XOOPS Kernel Object
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: */
12:
13: namespace Xoops\Core\Kernel;
14:
15: use Xoops\Core\Kernel\Dtype;
16:
17: /**
18: * Establish Xoops object datatype legacy defines
19: * New code should use Dtype::TYPE_* constants
20: *
21: * These will eventually be removed. See Xoops\Core\Kernel\Dtype for more.
22: */
23: define('XOBJ_DTYPE_TXTBOX', Dtype::TYPE_TEXT_BOX);
24: define('XOBJ_DTYPE_TXTAREA', Dtype::TYPE_TEXT_AREA);
25: define('XOBJ_DTYPE_INT', Dtype::TYPE_INTEGER);
26: define('XOBJ_DTYPE_URL', Dtype::TYPE_URL);
27: define('XOBJ_DTYPE_EMAIL', Dtype::TYPE_EMAIL);
28: define('XOBJ_DTYPE_ARRAY', Dtype::TYPE_ARRAY);
29: define('XOBJ_DTYPE_OTHER', Dtype::TYPE_OTHER);
30: define('XOBJ_DTYPE_SOURCE', Dtype::TYPE_SOURCE);
31: define('XOBJ_DTYPE_STIME', Dtype::TYPE_SHORT_TIME);
32: define('XOBJ_DTYPE_MTIME', Dtype::TYPE_MEDIUM_TIME);
33: define('XOBJ_DTYPE_LTIME', Dtype::TYPE_LONG_TIME);
34: define('XOBJ_DTYPE_FLOAT', Dtype::TYPE_FLOAT);
35: define('XOBJ_DTYPE_DECIMAL', Dtype::TYPE_DECIMAL);
36: define('XOBJ_DTYPE_ENUM', Dtype::TYPE_ENUM);
37:
38: /**
39: * Base class for all objects in the Xoops kernel (and beyond)
40: *
41: * @category Xoops\Core\Kernel\XoopsObject
42: * @package Xoops\Core\Kernel
43: * @author Kazumi Ono (AKA onokazu) <http://www.myweb.ne.jp/, http://jp.xoops.org/>
44: * @author Taiwen Jiang <phppp@users.sourceforge.net>
45: * @copyright 2000-2015 XOOPS Project (http://xoops.org)
46: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
47: * @link http://xoops.org
48: * @since 2.0.0
49: */
50: abstract class XoopsObject implements \ArrayAccess
51: {
52: /**
53: * holds all variables(properties) of an object
54: *
55: * @var array
56: */
57: public $vars = array();
58:
59: /**
60: * variables cleaned for store in DB
61: *
62: * @var array
63: */
64: public $cleanVars = array();
65:
66: /**
67: * is it a newly created object?
68: *
69: * @var bool
70: */
71: private $isNew = false;
72:
73: /**
74: * has any of the values been modified?
75: *
76: * @var bool
77: */
78: private $isDirty = false;
79:
80: /**
81: * errors
82: *
83: * @var array
84: */
85: private $errors = array();
86:
87: /**
88: * additional filters registered dynamically by a child class object
89: *
90: * @var array
91: */
92: private $filters = array();
93:
94: /**
95: * @var string
96: */
97: public $plugin_path;
98:
99: /**
100: * used for new/clone objects
101: *
102: * @return void
103: */
104: public function setNew()
105: {
106: $this->isNew = true;
107: }
108:
109: /**
110: * clear new flag
111: *
112: * @return void
113: */
114: public function unsetNew()
115: {
116: $this->isNew = false;
117: }
118:
119: /**
120: * check new flag
121: *
122: * @return bool
123: */
124: public function isNew()
125: {
126: return $this->isNew;
127: }
128:
129: /**
130: * mark modified objects as dirty
131: *
132: * used for modified objects only
133: *
134: * @return void
135: */
136: public function setDirty()
137: {
138: $this->isDirty = true;
139: }
140:
141: /**
142: * cleaar dirty flag
143: *
144: * @return void
145: */
146: public function unsetDirty()
147: {
148: $this->isDirty = false;
149: }
150:
151: /**
152: * check dirty flag
153: *
154: * @return bool
155: */
156: public function isDirty()
157: {
158: return $this->isDirty;
159: }
160:
161: /**
162: * initialize variables for the object
163: *
164: * @param string $key key
165: * @param int $data_type set to one of Dtype::TYPE_XXX constants (set to Dtype::TYPE_OTHER
166: * if no data type checking nor text sanitizing is required)
167: * @param mixed $value value
168: * @param bool $required require html form input?
169: * @param mixed $maxlength for Dtype::TYPE_TEXT_BOX type only
170: * @param string $options does this data have any select options?
171: *
172: * @return void
173: */
174: public function initVar($key, $data_type, $value = null, $required = false, $maxlength = null, $options = '')
175: {
176: $this->vars[$key] = array(
177: 'value' => $value,
178: 'required' => $required,
179: 'data_type' => $data_type,
180: 'maxlength' => $maxlength,
181: 'changed' => false,
182: 'options' => $options
183: );
184: }
185:
186: /**
187: * assign a value to a variable
188: *
189: * @param string $key name of the variable to assign
190: * @param mixed $value value to assign
191: *
192: * @return void
193: */
194: public function assignVar($key, $value)
195: {
196: if (isset($key) && isset($this->vars[$key])) {
197: $this->vars[$key]['value'] = $value;
198: }
199: }
200:
201: /**
202: * assign values to multiple variables in a batch
203: *
204: * @param array $var_arr associative array of values to assign
205: *
206: * @return void
207: */
208: public function assignVars($var_arr)
209: {
210: if (is_array($var_arr)) {
211: foreach ($var_arr as $key => $value) {
212: $this->assignVar($key, $value);
213: }
214: }
215: }
216:
217: /**
218: * assign a value to a variable
219: *
220: * @param string $key name of the variable to assign
221: * @param mixed $value value to assign
222: *
223: * @return void
224: */
225: public function setVar($key, $value)
226: {
227: if (!empty($key) && isset($value) && isset($this->vars[$key])) {
228: $this->vars[$key]['value'] = $value;
229: $this->vars[$key]['changed'] = true;
230: $this->setDirty();
231: }
232: }
233:
234: /**
235: * assign values to multiple variables in a batch
236: *
237: * @param array $var_arr associative array of values to assign
238: *
239: * @return void
240: */
241: public function setVars($var_arr)
242: {
243: if (is_array($var_arr)) {
244: foreach ($var_arr as $key => $value) {
245: $this->setVar($key, $value);
246: }
247: }
248: }
249:
250: /**
251: * unset variable(s) for the object
252: *
253: * @param mixed $var variable(s)
254: *
255: * @return bool
256: */
257: public function destroyVars($var)
258: {
259: if (empty($var)) {
260: return true;
261: }
262: $var = !is_array($var) ? array($var) : $var;
263: foreach ($var as $key) {
264: if (!isset($this->vars[$key])) {
265: continue;
266: }
267: $this->vars[$key]['changed'] = null;
268: }
269: return true;
270: }
271:
272: /**
273: * Assign values to multiple variables in a batch
274: *
275: * Meant for a CGI context:
276: * - prefixed CGI args are considered save
277: * - avoids polluting of namespace with CGI args
278: *
279: * @param mixed $var_arr associative array of values to assign
280: * @param string $pref prefix (only keys starting with the prefix will be set)
281: *
282: * @return void
283: */
284: public function setFormVars($var_arr = null, $pref = 'xo_')
285: {
286: $len = strlen($pref);
287: if (is_array($var_arr)) {
288: foreach ($var_arr as $key => $value) {
289: if ($pref == substr($key, 0, $len)) {
290: $this->setVar(substr($key, $len), $value);
291: }
292: }
293: }
294: }
295:
296: /**
297: * returns all variables for the object
298: *
299: * @return array associative array of key->value pairs
300: */
301: public function getVars()
302: {
303: return $this->vars;
304: }
305:
306: /**
307: * Returns the values of the specified variables
308: *
309: * @param mixed $keys An array containing the names of the keys to retrieve, or null to get all of them
310: * @param string $format Format to use (see getVar)
311: * @param int $maxDepth Maximum level of recursion to use if some vars are objects themselves
312: *
313: * @return array associative array of key->value pairs
314: */
315: public function getValues($keys = null, $format = Dtype::FORMAT_SHOW, $maxDepth = 1)
316: {
317: if (!isset($keys)) {
318: $keys = array_keys($this->vars);
319: }
320: $vars = array();
321: if (is_array($keys)) {
322: foreach ($keys as $key) {
323: if (isset($this->vars[$key])) {
324: if (is_object($this->vars[$key]) && is_a($this->vars[$key], 'Xoops\Core\Kernel\XoopsObject')) {
325: if ($maxDepth) {
326: /* @var $obj XoopsObject */
327: $obj = $this->vars[$key];
328: $vars[$key] = $obj->getValues(null, $format, $maxDepth - 1);
329: }
330: } else {
331: $vars[$key] = $this->getVar($key, $format);
332: }
333: }
334: }
335: }
336: return $vars;
337: }
338:
339: /**
340: * returns a specific variable for the object in a proper format
341: *
342: * @param string $key key of the object's variable to be returned
343: * @param string $format format to use for the output
344: *
345: * @return mixed formatted value of the variable
346: */
347: public function getVar($key, $format = Dtype::FORMAT_SHOW)
348: {
349: $ret = null;
350: if (!isset($this->vars[$key])) {
351: return $ret;
352: }
353: $ret = Dtype::getVar($this, $key, $format);
354: return $ret;
355: }
356:
357: /**
358: * clean values of all variables of the object for storage.
359: *
360: * @return bool true if successful
361: */
362: public function cleanVars()
363: {
364: $existing_errors = $this->getErrors();
365: $this->errors = array();
366: foreach ($this->vars as $k => $v) {
367: if (!$v['changed']) {
368: } else {
369: $this->cleanVars[$k] = Dtype::cleanVar($this, $k);
370: }
371: }
372: if (count($this->errors) > 0) {
373: $this->errors = array_merge($existing_errors, $this->errors);
374: return false;
375: }
376: // $this->_errors = array_merge($existing_errors, $this->_errors);
377: $this->unsetDirty();
378: return true;
379: }
380:
381: /**
382: * dynamically register additional filter for the object
383: *
384: * @param string $filtername name of the filter
385: *
386: * @return void
387: */
388: public function registerFilter($filtername)
389: {
390: $this->filters[] = $filtername;
391: }
392:
393: /**
394: * load all additional filters that have been registered to the object
395: *
396: * @return void
397: */
398: private function internalLoadFilters()
399: {
400: static $loaded;
401: if (isset($loaded)) {
402: return;
403: }
404: $loaded = 1;
405:
406: $path = empty($this->plugin_path) ? __DIR__ . '/filters' : $this->plugin_path;
407: if (\XoopsLoad::fileExists($file = $path . '/filter.php')) {
408: include_once $file;
409: if (is_array($this->filters)) {
410: foreach ($this->filters as $f) {
411: if (\XoopsLoad::fileExists($file = $path . '/' . strtolower($f) . 'php')) {
412: include_once $file;
413: }
414: }
415: }
416: }
417: }
418:
419: /**
420: * load all local filters for the object
421: *
422: * Filter distribution:
423: * In each module folder there is a folder "filter" containing filter files with,
424: * filename: [name_of_target_class][.][function/action_name][.php];
425: * function name: [dirname][_][name_of_target_class][_][function/action_name];
426: * parameter: the target object
427: *
428: * @param string $method function or action name
429: *
430: * @return void
431: */
432: public function loadFilters($method)
433: {
434: $this->internalLoadFilters();
435:
436: $class = get_class($this);
437: $modules_active = \Xoops::getInstance()->getActiveModules();
438: if (is_array($modules_active)) {
439: foreach ($modules_active as $dirname) {
440: $file = \XoopsBaseConfig::get('root-path') . '/modules/'
441: . $dirname . '/filter/' . $class . '.' . $method . '.php';
442: if (\XoopsLoad::fileExists($file)) {
443: include_once $file;
444: $function = $dirname . '_' . $class . '_' . $method;
445: if (function_exists($function)) {
446: call_user_func_array($function, array(&$this));
447: }
448: }
449: }
450: }
451: }
452:
453: /**
454: * create a clone(copy) of the current object
455: *
456: * @return object clone
457: */
458: public function xoopsClone()
459: {
460: $clone = clone $this;
461: return $clone;
462: }
463:
464: /**
465: * Adjust a newly cloned object
466: */
467: public function __clone()
468: {
469: // need this to notify the handler class that this is a newly created object
470: $this->setNew();
471: }
472:
473: /**
474: * add an error
475: *
476: * @param string $err_str to add
477: *
478: * @return void
479: */
480: public function setErrors($err_str)
481: {
482: if (is_array($err_str)) {
483: $this->errors = array_merge($this->errors, $err_str);
484: } else {
485: $this->errors[] = trim($err_str);
486: }
487: }
488:
489: /**
490: * return the errors for this object as an array
491: *
492: * @return array an array of errors
493: */
494: public function getErrors()
495: {
496: return $this->errors;
497: }
498:
499: /**
500: * return the errors for this object as html
501: *
502: * @return string html listing the errors
503: * @todo remove hardcoded HTML strings
504: */
505: public function getHtmlErrors()
506: {
507: $ret = '<h4>Errors</h4>';
508: if (!empty($this->errors)) {
509: foreach ($this->errors as $error) {
510: $ret .= $error . '<br />';
511: }
512: } else {
513: $ret .= 'None<br />';
514: }
515: return $ret;
516: }
517:
518: /**
519: * toArray
520: *
521: * @deprecated
522: * @return array
523: */
524: public function toArray()
525: {
526: return $this->getValues();
527: }
528:
529: /**
530: * ArrayAccess methods
531: */
532:
533: /**
534: * offsetExists
535: *
536: * @param mixed $offset array key
537: *
538: * @return bool true if offset exists
539: */
540: public function offsetExists($offset)
541: {
542: return isset($this->vars[$offset]);
543: }
544:
545: /**
546: * offsetGet
547: *
548: * @param mixed $offset array key
549: *
550: * @return mixed value
551: */
552: public function offsetGet($offset)
553: {
554: return $this->getVar($offset);
555: }
556:
557: /**
558: * offsetSet
559: *
560: * @param mixed $offset array key
561: * @param mixed $value
562: *
563: * @return void
564: */
565: public function offsetSet($offset, $value)
566: {
567: $this->setVar($offset, $value);
568: }
569:
570: /**
571: * offsetUnset
572: *
573: * @param mixed $offset array key
574: *
575: * @return void
576: */
577: public function offsetUnset($offset)
578: {
579: $this->vars[$offset]['value'] = null;
580: $this->vars[$offset]['changed'] = true;
581: $this->setDirty();
582: }
583: }
584: