1: <?php
2: /**
3: * Extended User Profile
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: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package profile
15: * @since 2.3.0
16: * @author Jan Pedersen
17: * @author Taiwen Jiang <phppp@users.sourceforge.net>
18: */
19:
20: // defined('XOOPS_ROOT_PATH') || exit("XOOPS root path not defined");
21:
22: /**
23: * @package kernel
24: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
25: */
26: class ProfileField extends XoopsObject
27: {
28: public $field_id;
29: public $cat_id;
30: public $field_type;
31: public $field_valuetype;
32: public $field_name;
33: public $field_title;
34: public $field_description;
35: public $field_required; //0 = no, 1 = yes
36: public $field_maxlength;
37: public $field_weight;
38: public $field_default;
39: public $field_notnull;
40: public $field_edit;
41: public $field_show;
42: public $field_config;
43: public $field_options;
44: public $step_id;
45:
46: /**
47: *
48: */
49: public function __construct()
50: {
51: $this->initVar('field_id', XOBJ_DTYPE_INT, null);
52: $this->initVar('cat_id', XOBJ_DTYPE_INT, null, true);
53: $this->initVar('field_type', XOBJ_DTYPE_TXTBOX);
54: $this->initVar('field_valuetype', XOBJ_DTYPE_INT, null, true);
55: $this->initVar('field_name', XOBJ_DTYPE_TXTBOX, null, true);
56: $this->initVar('field_title', XOBJ_DTYPE_TXTBOX);
57: $this->initVar('field_description', XOBJ_DTYPE_TXTAREA);
58: $this->initVar('field_required', XOBJ_DTYPE_INT, 0); //0 = no, 1 = yes
59: $this->initVar('field_maxlength', XOBJ_DTYPE_INT, 0);
60: $this->initVar('field_weight', XOBJ_DTYPE_INT, 0);
61: $this->initVar('field_default', XOBJ_DTYPE_TXTAREA, '');
62: $this->initVar('field_notnull', XOBJ_DTYPE_INT, 1);
63: $this->initVar('field_edit', XOBJ_DTYPE_INT, 0);
64: $this->initVar('field_show', XOBJ_DTYPE_INT, 0);
65: $this->initVar('field_config', XOBJ_DTYPE_INT, 0);
66: $this->initVar('field_options', XOBJ_DTYPE_ARRAY, array());
67: $this->initVar('step_id', XOBJ_DTYPE_INT, 0);
68: }
69:
70: /**
71: * Extra treatment dealing with non latin encoding
72: * Tricky solution
73: * @param string $key
74: * @param mixed $value
75: * @param bool $not_gpc
76: */
77: public function setVar($key, $value, $not_gpc = false)
78: {
79: if ($key === 'field_options' && \is_array($value)) {
80: foreach (array_keys($value) as $idx) {
81: $value[$idx] = base64_encode($value[$idx]);
82: }
83: }
84: parent::setVar($key, $value, $not_gpc);
85: }
86:
87: /**
88: * @param string $key
89: * @param string $format
90: *
91: * @return mixed
92: */
93: public function getVar($key, $format = 's')
94: {
95: $value = parent::getVar($key, $format);
96: if ($key === 'field_options' && !empty($value)) {
97: foreach (array_keys($value) as $idx) {
98: $value[$idx] = base64_decode($value[$idx]);
99: }
100: }
101:
102: return $value;
103: }
104:
105: /**
106: * Returns a {@link XoopsFormElement} for editing the value of this field
107: *
108: * @param XoopsUser $user {@link XoopsUser} object to edit the value of
109: * @param ProfileProfile $profile {@link ProfileProfile} object to edit the value of
110: *
111: * @return XoopsFormElement
112: **/
113: public function getEditElement($user, $profile)
114: {
115: $value = in_array($this->getVar('field_name'), $this->getUserVars()) ? $user->getVar($this->getVar('field_name'), 'e') : $profile->getVar($this->getVar('field_name'), 'e');
116:
117: $caption = $this->getVar('field_title');
118: $caption = defined($caption) ? constant($caption) : $caption;
119: $name = $this->getVar('field_name', 'e');
120: $options = $this->getVar('field_options');
121: if (is_array($options)) {
122: //asort($options);
123:
124: foreach (array_keys($options) as $key) {
125: $optval = defined($options[$key]) ? constant($options[$key]) : $options[$key];
126: $optkey = defined($key) ? constant($key) : $key;
127: unset($options[$key]);
128: $options[$optkey] = $optval;
129: }
130: }
131: include_once $GLOBALS['xoops']->path('class/xoopsformloader.php');
132: switch ($this->getVar('field_type')) {
133: default:
134: case 'autotext':
135: //autotext is not for editing
136: $element = new XoopsFormLabel($caption, $this->getOutputValue($user, $profile));
137: break;
138:
139: case 'textbox':
140: $element = new XoopsFormText($caption, $name, 35, $this->getVar('field_maxlength'), $value);
141: break;
142:
143: case 'textarea':
144: $element = new XoopsFormTextArea($caption, $name, $value, 4, 30);
145: break;
146:
147: case 'dhtml':
148: $element = new XoopsFormDhtmlTextArea($caption, $name, $value, 10, 30);
149: break;
150:
151: case 'select':
152: $element = new XoopsFormSelect($caption, $name, $value);
153: // If options do not include an empty element, then add a blank option to prevent any default selection
154: // if (!in_array('', array_keys($options))) {
155: if (!array_key_exists('', $options)) {
156: $element->addOption('', _NONE);
157:
158: $eltmsg = empty($caption) ? sprintf(_FORM_ENTER, $name) : sprintf(_FORM_ENTER, $caption);
159: $eltmsg = str_replace('"', '\"', stripslashes($eltmsg));
160: $element->customValidationCode[] = "\nvar hasSelected = false; var selectBox = myform.{$name};" . "for (i = 0; i < selectBox.options.length; i++) { if (selectBox.options[i].selected == true && selectBox.options[i].value != '') { hasSelected = true; break; } }" . "if (!hasSelected) { window.alert(\"{$eltmsg}\"); selectBox.focus(); return false; }";
161: }
162: $element->addOptionArray($options);
163: break;
164:
165: case 'select_multi':
166: $element = new XoopsFormSelect($caption, $name, $value, 5, true);
167: $element->addOptionArray($options);
168: break;
169:
170: case 'radio':
171: $element = new XoopsFormRadio($caption, $name, (string)$value);
172: $element->addOptionArray($options);
173: break;
174:
175: case 'checkbox':
176: $element = new XoopsFormCheckBox($caption, $name, $value);
177: $element->addOptionArray($options);
178: break;
179:
180: case 'yesno':
181: $element = new XoopsFormRadioYN($caption, $name, $value);
182: break;
183:
184: case 'group':
185: $element = new XoopsFormSelectGroup($caption, $name, true, $value);
186: break;
187:
188: case 'group_multi':
189: $element = new XoopsFormSelectGroup($caption, $name, true, $value, 5, true);
190: break;
191:
192: case 'language':
193: $element = new XoopsFormSelectLang($caption, $name, $value);
194: break;
195:
196: case 'date':
197: $element = new XoopsFormTextDateSelect($caption, $name, 15, $value);
198: break;
199:
200: case 'longdate':
201: $element = new XoopsFormTextDateSelect($caption, $name, 15, str_replace('-', '/', $value));
202: break;
203:
204: case 'datetime':
205: $element = new XoopsFormDatetime($caption, $name, 15, $value);
206: break;
207:
208: case 'timezone':
209: $element = new XoopsFormSelectTimezone($caption, $name, $value);
210: //$element->setExtra("style='width: 280px;'");
211: break;
212:
213: case 'rank':
214: $element = new XoopsFormSelect($caption, $name, $value);
215:
216: include_once $GLOBALS['xoops']->path('class/xoopslists.php');
217: $ranks = XoopsLists::getUserRankList();
218: $element->addOption(0, '--------------');
219: $element->addOptionArray($ranks);
220: break;
221:
222: case 'theme':
223: $element = new XoopsFormSelectTheme($caption, $name, $value, 1, true);
224: break;
225: }
226: if ($this->getVar('field_description') != '') {
227: $element->setDescription($this->getVar('field_description'));
228: }
229:
230: return $element;
231: }
232:
233: /**
234: * Returns a value for output of this field
235: *
236: * @param XoopsUser $user {@link XoopsUser} object to get the value of
237: * @param profileProfile $profile object to get the value of
238: *
239: * @return mixed
240: **/
241: public function getOutputValue(&$user, $profile)
242: {
243: xoops_loadLanguage('modinfo', 'profile');
244:
245: $value = in_array($this->getVar('field_name'), $this->getUserVars()) ? $user->getVar($this->getVar('field_name')) : $profile->getVar($this->getVar('field_name'));
246:
247: switch ($this->getVar('field_type')) {
248: default:
249: case 'textbox':
250: $value = is_array($value) ? $value[0] : $value;
251: if ($this->getVar('field_name') === 'url' && $value !== '') {
252: return '<a href="' . formatURL($value) . '" rel="external">' . $value . '</a>';
253: } else {
254: return $value;
255: }
256: break;
257: case 'textarea':
258: case 'dhtml':
259: case 'theme':
260: case 'language':
261: return $value;
262: break;
263:
264: case 'select':
265: case 'radio':
266: $value = is_array($value) ? $value[0] : $value;
267: $options = $this->getVar('field_options');
268: if (isset($options[$value])) {
269: $value = htmlspecialchars(defined($options[$value]) ? constant($options[$value]) : $options[$value], ENT_QUOTES);
270: } else {
271: $value = '';
272: }
273:
274: return $value;
275: break;
276:
277: case 'select_multi':
278: case 'checkbox':
279: $options = $this->getVar('field_options');
280: $ret = array();
281: if (count($options) > 0) {
282: foreach (array_keys($options) as $key) {
283: if (in_array($key, $value)) {
284: $ret[$key] = htmlspecialchars(defined($options[$key]) ? constant($options[$key]) : $options[$key], ENT_QUOTES);
285: }
286: }
287: }
288:
289: return $ret;
290: break;
291:
292: case 'group':
293: /** @var XoopsMemberHandler $member_handler */
294: $member_handler = xoops_getHandler('member');
295: $options = $member_handler->getGroupList();
296: $ret = isset($options[$value]) ? $options[$value] : '';
297:
298: return $ret;
299: break;
300:
301: case 'group_multi':
302: /** @var XoopsMemberHandler $member_handler */
303: $member_handler = xoops_getHandler('member');
304: $options = $member_handler->getGroupList();
305: $ret = array();
306: foreach (array_keys($options) as $key) {
307: if (in_array($key, $value)) {
308: $ret[$key] = htmlspecialchars($options[$key], ENT_QUOTES);
309: }
310: }
311:
312: return $ret;
313: break;
314:
315: case 'longdate':
316: //return YYYY/MM/DD format - not optimal as it is not using local date format, but how do we do that
317: //when we cannot convert it to a UNIX timestamp?
318: return str_replace('-', '/', $value);
319:
320: case 'date':
321: return formatTimestamp($value, 's');
322: break;
323:
324: case 'datetime':
325: if (!empty($value)) {
326: return formatTimestamp($value, 'm');
327: } else {
328: return $value = _PROFILE_MI_NEVER_LOGGED_IN;
329: }
330: break;
331:
332: case 'autotext':
333: $value = $user->getVar($this->getVar('field_name'), 'n'); //autotext can have HTML in it
334: $value = str_replace('{X_UID}', $user->getVar('uid'), $value);
335: $value = str_replace('{X_URL}', XOOPS_URL, $value);
336: $value = str_replace('{X_UNAME}', $user->getVar('uname'), $value);
337:
338: return $value;
339: break;
340:
341: case 'rank':
342: $userrank = $user->rank();
343: $user_rankimage = '';
344: if (isset($userrank['image']) && $userrank['image'] !== '') {
345: $user_rankimage = '<img src="' . XOOPS_UPLOAD_URL . '/' . $userrank['image'] . '" alt="' . $userrank['title'] . '" /><br>';
346: }
347:
348: return $user_rankimage . $userrank['title'];
349: break;
350:
351: case 'yesno':
352: return $value ? _YES : _NO;
353: break;
354:
355: case 'timezone':
356: include_once $GLOBALS['xoops']->path('class/xoopslists.php');
357: $timezones = XoopsLists::getTimeZoneList();
358: $value = empty($value) ? '0' : (string)$value;
359:
360: return $timezones[str_replace('.0', '', $value)];
361: break;
362: }
363: }
364:
365: /**
366: * Returns a value ready to be saved in the database
367: *
368: * @param mixed $value Value to format
369: *
370: * @return mixed
371: */
372: public function getValueForSave($value)
373: {
374: switch ($this->getVar('field_type')) {
375: default:
376: case 'textbox':
377: case 'textarea':
378: case 'dhtml':
379: case 'yesno':
380: case 'timezone':
381: case 'theme':
382: case 'language':
383: case 'select':
384: case 'radio':
385: case 'select_multi':
386: case 'group':
387: case 'group_multi':
388: case 'longdate':
389: return $value;
390:
391: case 'checkbox':
392: return (array)$value;
393:
394: case 'date':
395: if ($value !== '') {
396: return strtotime($value);
397: }
398:
399: return $value;
400: break;
401:
402: case 'datetime':
403: if (!empty($value)) {
404: return strtotime($value['date']) + (int)$value['time'];
405: }
406:
407: return $value;
408: break;
409: }
410: }
411:
412: /**
413: * Get names of user variables
414: *
415: * @return array
416: */
417: public function getUserVars()
418: {
419: /** @var ProfileProfileHandler $profile_handler */
420: $profile_handler = xoops_getModuleHandler('profile', 'profile');
421:
422: return $profile_handler->getUserVars();
423: }
424: }
425:
426: /**
427: * @package kernel
428: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
429: */
430: class ProfileFieldHandler extends XoopsPersistableObjectHandler
431: {
432: public $table_link;
433:
434: /**
435: * @param null|XoopsDatabase $db
436: */
437: public function __construct(XoopsDatabase $db)
438: {
439: parent::__construct($db, 'profile_field', 'profilefield', 'field_id', 'field_title');
440: }
441:
442: /**
443: * Read field information from cached storage
444: *
445: * @param bool $force_update read fields from database and not cached storage
446: *
447: * @return array
448: */
449: public function loadFields($force_update = false)
450: {
451: static $fields = array();
452: if (!empty($force_update) || count($fields) == 0) {
453: $this->table_link = $this->db->prefix('profile_category');
454: $criteria = new Criteria('o.field_id', 0, '!=');
455: $criteria->setSort('l.cat_weight ASC, o.field_weight');
456: $field_objs =& $this->getByLink($criteria, array('o.*'), true, 'cat_id', 'cat_id');
457: foreach (array_keys($field_objs) as $i) {
458: $fields[$field_objs[$i]->getVar('field_name')] = $field_objs[$i];
459: }
460: }
461:
462: return $fields;
463: }
464:
465: /**
466: * save a profile field in the database
467: *
468: * @param XoopsObject|ProfileField $obj reference to the object
469: * @param bool $force whether to force the query execution despite security settings
470: *
471: * @internal param bool $checkObject check if the object is dirty and clean the attributes
472: * @return bool FALSE if failed, TRUE if already present and unchanged or successful
473: */
474: public function insert(XoopsObject $obj, $force = false)
475: {
476: if (!($obj instanceof $this->className)) {
477: return false;
478: }
479: /** @var ProfileProfileHandler $profile_handler */
480: $profile_handler = xoops_getModuleHandler('profile', 'profile');
481: $obj->setVar('field_name', str_replace(' ', '_', $obj->getVar('field_name')));
482: $obj->cleanVars();
483: $defaultstring = '';
484: switch ($obj->getVar('field_type')) {
485: case 'datetime':
486: case 'date':
487: $obj->setVar('field_valuetype', XOBJ_DTYPE_INT);
488: $obj->setVar('field_maxlength', 10);
489: break;
490:
491: case 'longdate':
492: $obj->setVar('field_valuetype', XOBJ_DTYPE_MTIME);
493: break;
494:
495: case 'yesno':
496: $obj->setVar('field_valuetype', XOBJ_DTYPE_INT);
497: $obj->setVar('field_maxlength', 1);
498: break;
499:
500: case 'textbox':
501: if ($obj->getVar('field_valuetype') != XOBJ_DTYPE_INT) {
502: $obj->setVar('field_valuetype', XOBJ_DTYPE_TXTBOX);
503: }
504: break;
505:
506: case 'autotext':
507: if ($obj->getVar('field_valuetype') != XOBJ_DTYPE_INT) {
508: $obj->setVar('field_valuetype', XOBJ_DTYPE_TXTAREA);
509: }
510: break;
511:
512: case 'group_multi':
513: case 'select_multi':
514: case 'checkbox':
515: $obj->setVar('field_valuetype', XOBJ_DTYPE_ARRAY);
516: break;
517:
518: case 'language':
519: case 'timezone':
520: case 'theme':
521: $obj->setVar('field_valuetype', XOBJ_DTYPE_TXTBOX);
522: break;
523:
524: case 'dhtml':
525: case 'textarea':
526: $obj->setVar('field_valuetype', XOBJ_DTYPE_TXTAREA);
527: break;
528: }
529:
530: if ($obj->getVar('field_valuetype') === '') {
531: $obj->setVar('field_valuetype', XOBJ_DTYPE_TXTBOX);
532: }
533:
534: if ((!in_array($obj->getVar('field_name'), $this->getUserVars())) && isset($_REQUEST['field_required'])) {
535: if ($obj->isNew()) {
536: //add column to table
537: $changetype = 'ADD';
538: } else {
539: //update column information
540: $changetype = 'MODIFY COLUMN';
541: }
542: $maxlengthstring = $obj->getVar('field_maxlength') > 0 ? '(' . $obj->getVar('field_maxlength') . ')' : '';
543:
544: //set type
545: switch ($obj->getVar('field_valuetype')) {
546: default:
547: case XOBJ_DTYPE_ARRAY:
548: case XOBJ_DTYPE_UNICODE_ARRAY:
549: $type = 'mediumtext';
550: $maxlengthstring = '';
551: break;
552: case XOBJ_DTYPE_UNICODE_EMAIL:
553: case XOBJ_DTYPE_UNICODE_TXTBOX:
554: case XOBJ_DTYPE_UNICODE_URL:
555: case XOBJ_DTYPE_EMAIL:
556: case XOBJ_DTYPE_TXTBOX:
557: case XOBJ_DTYPE_URL:
558: $type = 'varchar';
559: // varchars must have a maxlength
560: if (!$maxlengthstring) {
561: //so set it to max if maxlength is not set - or should it fail?
562: $maxlengthstring = '(255)';
563: $obj->setVar('field_maxlength', 255);
564: }
565: break;
566:
567: case XOBJ_DTYPE_INT:
568: $type = 'int';
569: break;
570:
571: case XOBJ_DTYPE_DECIMAL:
572: $type = 'decimal(14,6)';
573: break;
574:
575: case XOBJ_DTYPE_FLOAT:
576: $type = 'float(15,9)';
577: break;
578:
579: case XOBJ_DTYPE_OTHER:
580: case XOBJ_DTYPE_UNICODE_TXTAREA:
581: case XOBJ_DTYPE_TXTAREA:
582: $type = 'text';
583: $maxlengthstring = '';
584: break;
585:
586: case XOBJ_DTYPE_MTIME:
587: $type = 'date';
588: $maxlengthstring = '';
589: break;
590: }
591:
592: $sql = 'ALTER TABLE `' . $profile_handler->table . '` ' . $changetype . ' `' . $obj->cleanVars['field_name'] . '` ' . $type . $maxlengthstring . ' NULL';
593: $result = $force ? $this->db->queryF($sql) : $this->db->query($sql);
594: if (!$result) {
595: $obj->setErrors($this->db->error());
596: return false;
597: }
598: }
599:
600: //change this to also update the cached field information storage
601: $obj->setDirty();
602: if (!parent::insert($obj, $force)) {
603: return false;
604: }
605:
606: return $obj->getVar('field_id');
607: }
608:
609: /**
610: * delete a profile field from the database
611: *
612: * @param XoopsObject|ProfileField $obj reference to the object to delete
613: * @param bool $force
614: * @return bool FALSE if failed.
615: **/
616: public function delete(XoopsObject $obj, $force = false)
617: {
618: if (!($obj instanceof $this->className)) {
619: return false;
620: }
621: /** @var ProfileProfileHandler $profile_handler */
622: $profile_handler = xoops_getModuleHandler('profile', 'profile');
623: // remove column from table
624: $sql = 'ALTER TABLE ' . $profile_handler->table . ' DROP `' . $obj->getVar('field_name', 'n') . '`';
625: if ($this->db->query($sql)) {
626: //change this to update the cached field information storage
627: if (!parent::delete($obj, $force)) {
628: return false;
629: }
630:
631: if ($obj->getVar('field_show') || $obj->getVar('field_edit')) {
632: /** @var XoopsModuleHandler $module_handler */
633: $module_handler = xoops_getHandler('module');
634: $profile_module = $module_handler->getByDirname('profile');
635: if (is_object($profile_module)) {
636: // Remove group permissions
637: /** @var XoopsGroupPermHandler $groupperm_handler */
638: $groupperm_handler = xoops_getHandler('groupperm');
639: $criteria = new CriteriaCompo(new Criteria('gperm_modid', $profile_module->getVar('mid')));
640: $criteria->add(new Criteria('gperm_itemid', $obj->getVar('field_id')));
641:
642: return $groupperm_handler->deleteAll($criteria);
643: }
644: }
645: }
646:
647: return false;
648: }
649:
650: /**
651: * Get array of standard variable names (user table)
652: *
653: * @return array
654: */
655: public function getUserVars()
656: {
657: return array(
658: 'uid',
659: 'uname',
660: 'name',
661: 'email',
662: 'url',
663: 'user_avatar',
664: 'user_regdate',
665: 'user_icq',
666: 'user_from',
667: 'user_sig',
668: 'user_viewemail',
669: 'actkey',
670: 'user_aim',
671: 'user_yim',
672: 'user_msnm',
673: 'pass',
674: 'posts',
675: 'attachsig',
676: 'rank',
677: 'level',
678: 'theme',
679: 'timezone_offset',
680: 'last_login',
681: 'umode',
682: 'uorder',
683: 'notify_method',
684: 'notify_mode',
685: 'user_occ',
686: 'bio',
687: 'user_intrest',
688: 'user_mailok');
689: }
690: }
691: