1<?php 2/* 3 * e107 website system 4 * 5 * Copyright (C) 2008-2011 e107 Inc (e107.org) 6 * Released under the terms and conditions of the 7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8 * 9 * User Model 10 * 11 * $URL$ 12 * $Id$ 13 */ 14 15/** 16 * @package e107 17 * @subpackage e107_handlers 18 * @version $Id$ 19 * @author SecretR 20 * 21 * Front-end User Models 22 */ 23 24if (!defined('e107_INIT')) 25{ 26 exit; 27} 28 29class e_user_model extends e_admin_model 30{ 31 /** 32 * Describes all model data, used as _FIELD_TYPE array as well 33 * @var array 34 */ 35 protected $_data_fields = array( 36 'user_id' => 'integer', 37 'user_name' => 'string', 38 'user_loginname' => 'string', 39 'user_customtitle' => 'string', 40 'user_password' => 'string', 41 'user_sess' => 'string', 42 'user_email' => 'string', 43 'user_signature' => 'string', 44 'user_image' => 'string', 45 'user_hideemail' => 'integer', 46 'user_join' => 'integer', 47 'user_lastvisit' => 'integer', 48 'user_currentvisit' => 'integer', 49 'user_lastpost' => 'integer', 50 'user_chats' => 'integer', 51 'user_comments' => 'integer', 52 'user_ip' => 'string', 53 'user_ban' => 'integer', 54 'user_prefs' => 'string', 55 'user_visits' => 'integer', 56 'user_admin' => 'integer', 57 'user_login' => 'string', 58 'user_class' => 'string', 59 'user_perms' => 'string', 60 'user_realm' => 'string', 61 'user_pwchange' => 'integer', 62 'user_xup' => 'string', 63 ); 64 65 /** 66 * Validate required fields 67 * @var array 68 */ 69 protected $_validation_rules = array( 70 'user_name' => array('string', '1', 'LAN_USER_01', 'LAN_USER_HELP_01'), // TODO - regex 71 'user_loginname' => array('string', '1', 'LAN_USER_02', 'LAN_USER_HELP_02'), // TODO - regex 72 'user_password' => array('compare', '5', 'LAN_PASSWORD', 'LAN_USER_HELP_05'), // TODO - pref - modify it somewhere below - prepare_rules()? 73 'user_email' => array('email', '', 'LAN_EMAIL', 'LAN_USER_HELP_08'), 74 ); 75 76 /** 77 * Validate optional fields - work in progress, not working yet 78 * @var array 79 */ 80 protected $_optional_rules = array( 81 'user_customtitle' => array('string', '1', 'LAN_USER_01'), // TODO - regex 82 ); 83 84 /** 85 * @see e_model 86 * @var string 87 */ 88 protected $_db_table = 'user'; 89 90 /** 91 * @see e_model 92 * @var string 93 */ 94 protected $_field_id = 'user_id'; 95 96 /** 97 * @see e_model 98 * @var string 99 */ 100 protected $_message_stack = 'user'; 101 102 /** 103 * User class as set in user Adminsitration 104 * 105 * @var integer 106 */ 107 protected $_memberlist_access = null; 108 109 /** 110 * Extended data 111 * 112 * @var e_user_extended_model 113 */ 114 protected $_extended_model = null; 115 116 /** 117 * Extended structure 118 * 119 * @var e_user_extended_structure 120 */ 121 protected $_extended_structure = null; 122 123 /** 124 * User preferences model 125 * @var e_user_pref 126 */ 127 protected $_user_config = null; 128 129 /** 130 * User model of current editor 131 * @var e_user_model 132 */ 133 protected $_editor = null; 134 135 protected $_class_list; 136 137 /** 138 * Constructor 139 * @param array $data 140 * @return void 141 */ 142 public function __construct($data = array()) 143 { 144 $this->_memberlist_access = e107::getPref('memberlist_access'); 145 parent::__construct($data); 146 } 147 148 /** 149 * Always return integer 150 * 151 * @see e107_handlers/e_model#getId() 152 */ 153 public function getId() 154 { 155 return (integer) parent::getId(); 156 } 157 158 /** 159 * Try display name, fall back to login name when empty (shouldn't happen) 160 */ 161 final public function getName($anon = false) 162 { 163 if($this->isUser()) 164 { 165 return ($this->get('user_name') ? $this->get('user_name') : $this->get('user_loginname')); 166 } 167 return $anon; 168 } 169 170 /** 171 * Display name getter. Use it as DB field name will be changed soon. 172 */ 173 final public function getDisplayName() 174 { 175 return $this->get('user_name'); 176 } 177 178 /** 179 * Login name getter. Use it as DB field name will be changed soon. 180 */ 181 final public function getLoginName() 182 { 183 return $this->get('user_loginname'); 184 } 185 186 /** 187 * Real name getter. Use it as DB field name will be changed soon. 188 * @param bool $strict if false, fall back to Display name when empty 189 * @return mixed 190 */ 191 final public function getRealName($strict = false) 192 { 193 if($strict) return $this->get('user_login'); 194 return ($this->get('user_login') ? $this->get('user_login') : $this->get('user_name')); 195 } 196 197 final public function getAdminId() 198 { 199 return ($this->isAdmin() ? $this->getId() : false); 200 } 201 202 final public function getAdminName() 203 { 204 return ($this->isAdmin() ? $this->get('user_name') : false); 205 } 206 207 final public function getAdminEmail() 208 { 209 return ($this->isAdmin() ? $this->get('user_email') : false); 210 } 211 212 final public function getAdminPwchange() 213 { 214 return ($this->isAdmin() ? $this->get('user_pwchange') : false); 215 } 216 217 final public function getAdminPerms() 218 { 219 return ($this->isAdmin() ? $this->get('user_perms') : false); 220 } 221 222 final public function getTimezone() 223 { 224 // If timezone is not set, we return an empty string in order to use the 225 // default timezone is set for e107. 226 return ($this->get('user_timezone') ? $this->get('user_timezone') : ''); 227 } 228 229 /** 230 * DEPRECATED - will be removed or changed soon (see e_session) 231 * @return string 232 */ 233 public function getToken() 234 { 235 if(null === $this->get('user_token')) 236 { 237 //$this->set('user_token', md5($this->get('user_password').$this->get('user_lastvisit').$this->get('user_pwchange').$this->get('user_class'))); 238 $this->set('user_token', e107::getSession()->getFormToken(false)); 239 } 240 return $this->get('user_token'); 241 } 242 243 public static function randomKey() 244 { 245 return md5(uniqid(rand(), 1)); 246 } 247 248 public function isCurrent() 249 { 250 return false; 251 } 252 253 final public function isAdmin() 254 { 255 return ($this->get('user_admin') ? true : false); 256 } 257 258 final public function isNewUser() 259 { 260 $new_user_period = e107::getPref('user_new_period', 0); 261 262 if(empty($new_user_period)) { return false; } 263 264 return (($this->get('user_join') > strtotime($new_user_period." days ago")) ? true : false); 265 } 266 267 final public function isBot() 268 { 269 $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; 270 271 if(empty($userAgent)) 272 { 273 return false; 274 } 275 276 $botlist = array( "googlebot", "Bingbot", 'slurp', 'baidu', 'ichiro','nutch','yacy', "Teoma", 277 "alexa", "froogle", "Gigabot", "inktomi", 278 "looksmart", "URL_Spider_SQL", "Firefly", "NationalDirectory", 279 "Ask Jeeves", "TECNOSEEK", "InfoSeek", "WebFindBot", "girafabot", 280 "crawler", "www.galaxy.com", "Scooter", "msnbot", "appie", "FAST", "WebBug", "Spade", "ZyBorg", "rabaz", 281 "Baiduspider", "Feedfetcher-Google", "TechnoratiSnoop", "Rankivabot", 282 "Mediapartners-Google", "Sogou web spider", "WebAlta Crawler","TweetmemeBot", 283 "Butterfly","Twitturls","Me.dium","Twiceler"); 284 285 foreach($botlist as $bot) 286 { 287 if(stripos($userAgent, $bot) !== false){ return true; } 288 } 289 290 return false; 291 } 292 293 final public function isMainAdmin() 294 { 295 return $this->checkAdminPerms('0'); 296 } 297 298 final public function isUser() 299 { 300 return ($this->getId() ? true : false); 301 } 302 303 final public function isGuest() 304 { 305 return ($this->getId() ? false : true); 306 } 307 308 final public function hasBan() 309 { 310 return ((integer)$this->get('user_ban') === 1 ? true : false); 311 } 312 313 final public function hasRestriction() 314 { 315 return ((integer)$this->get('user_ban') === 0 ? false : true); 316 } 317 318 public function hasEditor() 319 { 320 return (null !== $this->_editor); 321 } 322 323 final protected function _setClassList() 324 { 325 $this->_class_list = array(); 326 if ($this->isUser()) 327 { 328 if ($this->get('user_class')) 329 { 330 // list of all 'inherited' user classes, convert elements to integer 331 $this->_class_list = array_map('intval', e107::getUserClass()->get_all_user_classes($this->get('user_class'), true)); 332 } 333 334 $this->_class_list[] = e_UC_MEMBER; 335 336 if($this->isNewUser()) 337 { 338 $this->_class_list[] = e_UC_NEWUSER; 339 } 340 341 if ($this->isAdmin()) 342 { 343 $this->_class_list[] = e_UC_ADMIN; 344 } 345 346 if ($this->isMainAdmin()) 347 { 348 $this->_class_list[] = e_UC_MAINADMIN; 349 } 350 } 351 else 352 { 353 $this->_class_list[] = e_UC_GUEST; 354 355 if($this->isBot()) 356 { 357 $this->_class_list[] = e_UC_BOTS; 358 } 359 360 } 361 362 $this->_class_list[] = e_UC_READONLY; 363 $this->_class_list[] = e_UC_PUBLIC; 364 365 // unique, rebuild indexes 366 $this->_class_list = array_merge(array_unique($this->_class_list)); 367 return $this; 368 } 369 370 /** 371 * @param bool $toString 372 * @return string|array 373 */ 374 final public function getClassList($toString = false) 375 { 376 if (null === $this->_class_list) 377 { 378 $this->_setClassList(); 379 } 380 return ($toString ? implode(',', $this->_class_list) : $this->_class_list); 381 } 382 383 final public function getClassRegex() 384 { 385 return '(^|,)('.str_replace(',', '|', $this->getClassList(true)).')(,|$)'; 386 } 387 388 final public function checkClass($class, $allowMain = true) 389 { 390 // FIXME - replace check_class() here 391 return (($allowMain && $this->isMainAdmin()) || check_class($class, $this->getClassList(), 0)); 392 } 393 394 final public function checkAdminPerms($perm_str) 395 { 396 // FIXME - method to replace getperms() 397 return ($this->isAdmin() && getperms($perm_str, $this->getAdminPerms())); 398 } 399 400 final public function checkEditorPerms($class = '') 401 { 402 if (!$this->hasEditor()) 403 return false; 404 405 $editor = $this->getEditor(); 406 407 if ('' !== $class) 408 return ($editor->isAdmin() && $editor->checkClass($class)); 409 410 return $editor->isAdmin(); 411 } 412 413 /** 414 * Check passed value against current user token 415 * DEPRECATED - will be removed or changed soon (see e_core_session) 416 * @param string $token md5 sum of e.g. posted token 417 * @return boolean 418 */ 419 final public function checkToken($token) 420 { 421 $utoken = $this->getToken(); 422 return (null !== $utoken && $token === md5($utoken)); 423 } 424 425 /** 426 * Bad but required (BC) method of retrieving all user data 427 * It's here to be used from get_user_data() core function. 428 * DON'T USE THEM BOTH unless you have VERY good reason to do it. 429 * 430 * @return array 431 */ 432 public function getUserData() 433 { 434 // revised - don't call extended object, no permission checks, just return joined user data 435 $ret = $this->getData(); 436 // $ret = array_merge($this->getExtendedModel()->getExtendedData(), $this->getData()); 437 if ($ret['user_perms'] == '0.') $ret['user_perms'] = '0'; 438 $ret['user_baseclasslist'] = $ret['user_class']; 439 $ret['user_class'] = $this->getClassList(true); 440 return $ret; 441 } 442 443 /** 444 * Check if given field name is present in core user table structure 445 * 446 * @param string $field 447 * @param boolean $short 448 * @return boolean 449 */ 450 public function isCoreField($field, $short = true) 451 { 452 if($short) $field = 'user_'.$field; 453 return isset($this->_data_fields[$field]); 454 } 455 456 /** 457 * Check if given field name is present in extended user table structure 458 * 459 * @param string $field 460 * @param boolean $short 461 * @return boolean 462 */ 463 public function isExtendedField($field, $short = true) 464 { 465 if($short) $field = 'user_'.$field; 466 if($this->isCoreField($field, false)) 467 { 468 return false; 469 } 470 return $this->getExtendedModel()->isField($field, false); 471 } 472 473 /** 474 * Get User value from core user table. 475 * This method doesn't perform any read permission cheks. 476 * 477 * @param string $field 478 * @param mixed $default 479 * @param boolean $short if true, 'user_' prefix will be added to field name 480 * @return mixed if field is not part of core user table returns null by default 481 */ 482 public function getCore($field, $default = null, $short = true) 483 { 484 if($short) $field = 'user_'.$field; 485 if($this->isCoreField($field, false)) return $this->get($field, $default); 486 return $default; 487 } 488 489 /** 490 * Set User value (core user field). 491 * This method doesn't perform any write permission cheks. 492 * 493 * @param string $field 494 * @param mixed $value 495 * @param boolean $short if true, 'user_' prefix will be added to field name 496 * @param boolean $strict if false no Applicable check will be made 497 * @return e_user_model 498 */ 499 public function setCore($field, $value, $short = true, $strict = false) 500 { 501 if($short) $field = 'user_'.$field; 502 if($this->isCoreField($field, false)) $this->set($field, $value, $strict); 503 return $this; 504 } 505 506 /** 507 * Get User extended value. 508 * This method doesn't perform any read permission cheks. 509 * 510 * @param string $field 511 * @param boolean $short if true, 'user_' prefix will be added to field name 512 * @param boolean $raw get raw DB values (no SQL query) 513 * @return mixed 514 */ 515 public function getExtended($field, $short = true, $raw = true) 516 { 517 return $this->getExtendedModel()->getSystem($field, $short, $raw); 518 } 519 520 /** 521 * Set User extended value. 522 * This method doesn't perform any write permission cheks. 523 * 524 * @param string $field 525 * @param mixed $value 526 * @param boolean $short if true, 'user_' prefix will be added to field name 527 * @param boolean $strict if false no Applicable check will be made 528 * @return e_user_model 529 */ 530 public function setExtended($field, $value, $short = true, $strict = false) 531 { 532 $this->getExtendedModel()->setSystem($field, $value, $short, $strict); 533 return $this; 534 } 535 536 /** 537 * Get User extended value after checking read permissions against current Editor 538 * 539 * @param string $field 540 * @param boolean $short if true, 'user_' prefix will be added to field name 541 * @param boolean $raw get raw DB values (no SQL query) 542 * @return mixed 543 */ 544 public function getExtendedFront($field, $short = true, $raw = false) 545 { 546 return $this->getExtendedModel()->getValue($field, $short, $raw); 547 } 548 549 /** 550 * Set User extended value after checking write permissions against current Editor. 551 * 552 * @param string $field 553 * @param mixed $value 554 * @param boolean $short if true, 'user_' prefix will be added to field name 555 * @return e_user_model 556 */ 557 public function setExtendedFront($field, $value, $short = true) 558 { 559 $this->getExtendedModel()->setValue($field, $value, $short); 560 return $this; 561 } 562 563 /** 564 * Transparent front-end getter. It performs all required read/applicable permission checks 565 * against current editor/user. It doesn't distinguish core and extended fields. 566 * It grants BC. 567 * It's what you'd need in all front-end parsing code (e.g. shortcodes) 568 * 569 * @param string $field 570 * @param mixed $default 571 * @param boolean $short if true, 'user_' prefix will be added to field name 572 * @param boolean $rawExtended get raw DB values (no SQL query) - used only for extended fields 573 * @return mixed if field is not readable returns null by default 574 */ 575 public function getValue($field, $default = null, $short = true, $rawExtended = false) 576 { 577 if($short) 578 { 579 $mfield = $field; 580 $field = 'user_'.$field; 581 } 582 else 583 { 584 $mfield = substr($field, 5); 585 } 586 587 // check for BC/override method first e.g. getSingatureValue($default, $system = false, $rawExtended); 588 $method = 'get'.ucfirst($mfield).'Value'; 589 if(method_exists($this, $method)) return $this->$method($default, false, $rawExtended); 590 591 if($this->isCoreField($field, false)) 592 { 593 if(!$this->isReadable($field)) return $default; 594 return $this->getCore($field, $default, false); 595 } 596 597 return $this->getExtendedFront($field, false, $rawExtended); 598 } 599 600 /** 601 * Transparent front-end setter. It performs all required write/applicable permission checks 602 * against current editor/user. It doesn't distinguish core and extended fields. 603 * It grants BC. 604 * It's what you'd need on all user front-end manipulation events (e.g. user settings page related code) 605 * NOTE: untrusted data should be provided via setPosted() method! 606 * 607 * @param string $field 608 * @param mixed $value 609 * @param boolean $short if true, 'user_' prefix will be added to field name 610 * @return e_user_model 611 */ 612 public function setValue($field, $value, $short = true) 613 { 614 if($short) 615 { 616 $mfield = $field; 617 $field = 'user_'.$field; 618 } 619 else 620 { 621 $mfield = substr($field, 5); 622 } 623 624 // check for BC/override method first e.g. setSingatureValue($value, $system = false); 625 $method = 'set'.ucfirst($mfield).'Value'; 626 if(method_exists($this, $method)) 627 { 628 $this->$method($value, false); 629 return $this; 630 } 631 632 if($this->isCoreField($field, false)) 633 { 634 if($this->isWritable($field)) $this->setCore($field, $value, false, true); 635 } 636 else 637 { 638 $this->setExtendedFront($field, $value, false); 639 } 640 641 return $this; 642 } 643 644 /** 645 * Transparent system getter. It doesn't perform any read/applicable permission checks 646 * against current editor/user. It doesn't distinguish core and extended fields. 647 * It grants BC. 648 * It's here to serve in your application logic. 649 * 650 * @param string $field 651 * @param mixed $default 652 * @param boolean $short if true, 'user_' prefix will be added to field name 653 * @param boolean $rawExtended get raw DB values (no SQL query) - used only for extended fields 654 * @return mixed 655 */ 656 public function getSystem($field, $default = null, $short = true, $rawExtended = true) 657 { 658 if($short) 659 { 660 $mfield = $field; 661 $field = 'user_'.$field; 662 } 663 else 664 { 665 $mfield = substr($field, 5); 666 } 667 668 // check for BC/override method first e.g. getSingatureValue($default, $system = true, $rawExtended); 669 $method = 'get'.ucfirst($mfield).'Value'; 670 if(method_exists($this, $method)) return $this->$method($default, true, $rawExtended); 671 672 if($this->isCoreField($field, false)) 673 { 674 return $this->getCore($field, $default, false); 675 } 676 677 return $this->getExtended($field, false, $rawExtended); 678 } 679 680 /** 681 * Transparent front-end setter. It doesn't perform any write/applicable permission checks 682 * against current editor/user. It doesn't distinguish core and extended fields. 683 * It's here to serve in your application logic. 684 * NOTE: untrusted data should be provided via setPosted() method! 685 * 686 * @param string $field 687 * @param mixed $value 688 * @param boolean $short if true, 'user_' prefix will be added to field name 689 * @param boolean $strict if false no Applicable check will be made 690 * @return e_user_model 691 */ 692 public function setSystem($field, $value, $short = true, $strict = false) 693 { 694 if($short) 695 { 696 $mfield = $field; 697 $field = 'user_'.$field; 698 } 699 else 700 { 701 $mfield = substr($field, 5); 702 } 703 704 // check for BC/override method first e.g. setSingatureValue($value, $system = true); 705 $method = 'set'.ucfirst($mfield).'Value'; 706 if(method_exists($this, $method)) 707 { 708 $this->$method($value, true); 709 return $this; 710 } 711 712 if($this->isCoreField($field, false)) 713 { 714 $this->setCore($field, $value, false, $strict); 715 } 716 else 717 { 718 $this->setExtended($field, $value, false, $strict); 719 } 720 721 return $this; 722 } 723 724 /** 725 * Just an example override method. This method is auto-magically called by getValue/System 726 * getters. 727 * $rawExtended is not used (here for example purposes only) 728 * If user_signature become extended field one day, we'd need this method 729 * for real - it'll call extended getters to retrieve the required value. 730 * 731 * @param mixed $default optional 732 * @param boolean $system optional 733 * @param boolean $rawExtended optional 734 * @return mixed value 735 */ 736 public function getSignatureValue($default = null, $system = false, $rawExtended = true) 737 { 738 if($system || $this->isReadable('user_signature')) return $this->getCore('signature', $default); 739 return $default; 740 } 741 742 /** 743 * Just an example override method. This method is auto-magically called by setValue/System 744 * setters. 745 * If user_signature become extended field one day, we'd need this method 746 * for real - it'll call extended setters to set the new signature value 747 * 748 * @param string $value 749 * @param boolean $system 750 * @return e_user_model 751 */ 752 public function setSignatureValue($value, $system = false) 753 { 754 if($system || $this->isWritable('user_signature')) $this->setCore('signature', $value); 755 return $this; 756 } 757 758 /** 759 * Get user preference 760 * @param string $pref_name 761 * @param mixed $default 762 * @return mixed 763 */ 764 public function getPref($pref_name = null, $default = null) 765 { 766 if(null === $pref_name) return $this->getConfig()->getData(); 767 return $this->getConfig()->get($pref_name, $default); 768 } 769 770 /** 771 * Set user preference 772 * @param string $pref_name 773 * @param mixed $value 774 * @return e_user_model 775 */ 776 public function setPref($pref_name, $value = null) 777 { 778 $this->getConfig()->set($pref_name, $value); 779 return $this; 780 } 781 782 /** 783 * Get user preference (advanced - slower) 784 * @param string $pref_path 785 * @param mixed $default 786 * @param integer $index if number, value will be exploded by "\n" and corresponding index will be returned 787 * @return mixed 788 */ 789 public function findPref($pref_path = null, $default = null, $index = null) 790 { 791 return $this->getConfig()->getData($pref_path, $default, $index); 792 } 793 794 /** 795 * Set user preference (advanced - slower) 796 * @param string $pref_path 797 * @param mixed $value 798 * @return e_user_model 799 */ 800 public function setPrefData($pref_path, $value = null) 801 { 802 $this->getConfig()->setData($pref_path, $value = null); 803 return $this; 804 } 805 806 /** 807 * New - External login providers support 808 * @return string Provider name 809 */ 810 public function getProviderName() 811 { 812 if($this->get('user_xup')) 813 { 814 $provider = explode('_', $this->get('user_xup')); 815 return array_shift($provider); 816 } 817 return null; 818 } 819 820 /** 821 * New - External login providers support 822 * @return boolean Check if there is external provider data 823 */ 824 public function hasProviderName() 825 { 826 return $this->has('user_xup'); 827 } 828 829 /** 830 * Get user extended model 831 * 832 * @return e_user_extended_model 833 */ 834 public function getExtendedModel() 835 { 836 if (null === $this->_extended_model) 837 { 838 $this->_extended_model = new e_user_extended_model($this); 839 } 840 return $this->_extended_model; 841 } 842 843 /** 844 * Set user extended model 845 * 846 * @param e_user_extended_model $extended_model 847 * @return e_user_model 848 */ 849 public function setExtendedModel($extended_model) 850 { 851 $this->_extended_model = $extended_model; 852 return $this; 853 } 854 855 /** 856 * Get user config model 857 * 858 * @return e_user_pref 859 */ 860 public function getConfig() 861 { 862 if (null === $this->_user_config) 863 { 864 $this->_user_config = new e_user_pref($this); 865 } 866 return $this->_user_config; 867 } 868 869 /** 870 * Set user config model 871 * 872 * @param e_user_pref $user_config 873 * @return e_user_model 874 */ 875 public function setConfig(e_user_pref $user_config) 876 { 877 $this->_user_config = $user_config; 878 return $this; 879 } 880 881 /** 882 * Get current user editor model 883 * @return e_user_model 884 */ 885 public function getEditor() 886 { 887 return $this->_editor; 888 } 889 890 /** 891 * Set current user editor model 892 * @return e_user_model 893 */ 894 public function setEditor(e_user_model $user_model) 895 { 896 $this->_editor = $user_model; 897 return $this; 898 } 899 900 /** 901 * Check if passed field is writable 902 * @param string $field 903 * @return boolean 904 */ 905 public function isWritable($field) 906 { 907 $perm = false; 908 $editor = $this->getEditor(); 909 if($this->getId() === $editor->getId() || $editor->isMainAdmin() || $editor->checkAdminPerms('4')) 910 $perm = true; 911 return ($perm && !in_array($field, array($this->getFieldIdName(), 'user_admin', 'user_perms', 'user_prefs'))); 912 } 913 914 /** 915 * Check if passed field is readable by the Editor 916 * @param string $field 917 * @return boolean 918 */ 919 public function isReadable($field) 920 { 921 $perm = false; 922 $editor = $this->getEditor(); 923 if($this->getId() === $editor->getId() || $editor->isMainAdmin() || $editor->checkAdminPerms('4')) 924 $perm = true; 925 return ($perm || (!in_array($field, array('user_admin', 'user_perms', 'user_prefs', 'user_password') && $editor->checkClass($this->_memberlist_access)))); 926 } 927 928 /** 929 * Set current object as a target 930 * 931 * @return e_user_model 932 */ 933 protected function setAsTarget() 934 { 935 e107::setRegistry('core/e107/user/'.$this->getId(), $this); 936 return $this; 937 } 938 939 /** 940 * Clear registered target 941 * 942 * @return e_user_model 943 */ 944 protected function clearTarget() 945 { 946 e107::setRegistry('core/e107/user'.$this->getId(), null); 947 return $this; 948 } 949 950 /** 951 * @see e_model#load($id, $force) 952 */ 953 public function load($user_id = 0, $force = false) 954 { 955 $qry = "SELECT u.*, ue.* FROM #user AS u LEFT JOIN #user_extended as ue ON u.user_id=ue.user_extended_id WHERE u.user_id={ID}"; 956 $this->setParam('db_query', $qry); 957 parent::load($user_id, $force); 958 if ($this->getId()) 959 { 960 // no errors - register 961 $this->setAsTarget() 962 ->setEditor(e107::getUser()); //set current user as default editor 963 } 964 } 965 966 /** 967 * Additional security while applying posted 968 * data to user model 969 * @return e_user_model 970 */ 971 public function mergePostedData($strict = true, $sanitize = true, $validate = true) 972 { 973 $posted = $this->getPostedData(); 974 foreach ($posted as $key => $value) 975 { 976 if(!$this->isWritable($key)) 977 { 978 $this->removePosted($key); 979 continue; 980 } 981 $this->_modifyPostedData($key, $value); 982 } 983 parent::mergePostedData(true, true, true); 984 return $this; 985 } 986 987 protected function _modifyPostedData($key, $value) 988 { 989 // TODO - add more here 990 switch ($key) 991 { 992 case 'password1': 993 // compare validation rule 994 $this->setPosted('user_password', array($value, $this->getPosted('password2'))); 995 break; 996 } 997 } 998 999 /** 1000 * Send model data to DB 1001 */ 1002 public function save($noEditorCheck = false, $force = false, $session = false) 1003 { 1004 if (!$noEditorCheck && !$this->checkEditorPerms()) 1005 { 1006 return false; // TODO - message, admin log 1007 } 1008 1009 // sync user prefs 1010 $this->getConfig()->apply(); 1011 1012 // TODO - do the save manually in this order: validate() on user model, save() on extended fields, save() on user model 1013 $ret = parent::save(true, $force, $session); 1014 1015 if(false !== $ret && null !== $this->_extended_model) // don't load extended fields if not already used 1016 { 1017 $ret_e = $this->_extended_model->save(true, $force, $session); 1018 if(false !== $ret_e) 1019 { 1020 return ($ret_e + $ret); 1021 } 1022 return false; 1023 } 1024 return $ret; 1025 } 1026 1027 public function saveDebug($extended = true, $return = false, $undo = true) 1028 { 1029 $ret = array(); 1030 $ret['CORE_FIELDS'] = parent::saveDebug(true, $undo); 1031 if($extended && null !== $this->_extended_model) 1032 { 1033 $ret['EXTENDED_FIELDS'] = $this->_extended_model->saveDebug(true, $undo); 1034 } 1035 1036 if($return) return $ret; 1037 print_a($ret); 1038 } 1039 1040 public function destroy() 1041 { 1042 $this->clearTarget() 1043 ->removeData(); 1044 1045 $this->_class_list = array(); 1046 $this->_editor = null; 1047 $this->_extended_structure = null; 1048 $this->_user_config = null; 1049 1050 if (null !== $this->_extended_model) 1051 { 1052 $this->_extended_model->destroy(); 1053 $this->_extended_model = null; 1054 } 1055 } 1056 1057 1058 /** 1059 * Add userclass to user and save. 1060 * @param null $userClassId 1061 * @return bool 1062 */ 1063 public function addClass($userClassId=null) 1064 { 1065 if(empty($userClassId)) 1066 { 1067 return false; 1068 } 1069 1070// $curClasses = explode(",", $this->getData('user_class')); 1071// $curClasses[] = $userClassId; 1072// $curClasses = array_unique($curClasses); 1073// 1074// $insert = implode(",", $curClasses); 1075 1076 //FIXME - @SecretR - I'm missing something here with setCore() etc. 1077 // $this->setCore('user_class',$insert ); 1078 // $this->saveDebug(false); 1079 1080 // Switched to unified remove user class method 1081 $insert = e107::getUserClass()->ucAdd($userClassId, $this->getData('user_class'), false); 1082 1083 if(!$uid = $this->getData('user_id')) 1084 { 1085 return false; 1086 } 1087 1088 return e107::getDb()->update('user',"user_class='".$insert."' WHERE user_id = ".$uid." LIMIT 1"); 1089 1090 } 1091 1092 1093 /** 1094 * Remove a userclass from the user. 1095 * @param null $userClassId 1096 * @return bool 1097 */ 1098 public function removeClass($userClassId=null) 1099 { 1100 if(empty($userClassId)) 1101 { 1102 return false; 1103 } 1104 1105// $curClasses = explode(",", $this->getData('user_class')); 1106// 1107// foreach($curClasses as $k=>$v) 1108// { 1109// if($v == $userClassId) 1110// { 1111// unset($curClasses[$k]); 1112// } 1113// } 1114 1115// $uid = $this->getData('user_id'); 1116 1117// $insert = implode(",", $curClasses); 1118 1119 // Switched to unified remove user class method 1120 $insert = e107::getUserClass()->ucRemove($userClassId, $this->getData('user_class'), false); 1121 1122 if(!$uid = $this->getData('user_id')) 1123 { 1124 return false; 1125 } 1126 1127 return e107::getDb()->update('user',"user_class='".$insert."' WHERE user_id = ".$uid." LIMIT 1"); 1128 1129 1130 } 1131 1132 1133} 1134 1135// TODO - add some more useful methods, sc_* methods support 1136class e_system_user extends e_user_model 1137{ 1138 public $debug = false; 1139 /** 1140 * Constructor 1141 * 1142 * @param array $user_data trusted data, loaded from DB 1143 * @return void 1144 */ 1145 public function __construct($user_data = array()) 1146 { 1147 parent::__construct($user_data); 1148 $this->setEditor(e107::getUser()); 1149 } 1150 1151 /** 1152 * Returns always false 1153 * Even if user data belongs to the current user, Current User interface 1154 * is not available 1155 * 1156 * @return boolean 1157 */ 1158 final public function isCurrent() 1159 { 1160 // check against current system user 1161 //return ($this->getId() && $this->getId() == e107::getUser()->getId()); 1162 return false; 1163 } 1164 1165 /** 1166 * Send user email 1167 * @param mixed $userInfo array data or null for current logged in user or any object subclass of e_object (@see e_system_user::renderEmail() for field requirements) 1168 */ 1169 public function email($type = 'email', $options = array(), $userInfo = null) 1170 { 1171 1172 if(null === $userInfo) 1173 { 1174 $userInfo = $this->getData(); 1175 } 1176 elseif(is_object($userInfo) && get_class($userInfo) == 'e_object' || is_subclass_of($userInfo, 'e_object')) 1177 { 1178 $userInfo = $userInfo->getData(); 1179 } 1180 1181 if(empty($userInfo) || !vartrue($userInfo['user_email'])) return false; 1182 1183 // plain password could be passed only via $options 1184 unset($userInfo['user_password']); 1185 if($options && is_array($options)) 1186 { 1187 $userInfo = array_merge($options, $userInfo); 1188 } 1189 1190 $eml = $this->renderEmail($type, $userInfo); 1191 1192 1193 1194 if(empty($eml)) 1195 { 1196 if($this->debug) 1197 { 1198 echo '$eml returned nothing on Line '.__LINE__.' of user_model.php using $type = '.$type; 1199 print_a($userInfo); 1200 } 1201 return false; 1202 } 1203 else 1204 { 1205 if($this->debug) 1206 { 1207 echo '<h3>$eml array</h3>'; 1208 print_a($eml); 1209 $temp = var_export($eml, true); 1210 print_a($temp); 1211 } 1212 } 1213 1214 $mailer = e107::getEmail(); 1215 1216 $mailer->template = $eml['template']; 1217 1218 1219 // Custom e107 Header 1220 if($userInfo['user_id']) 1221 { 1222 $eml['e107_header'] = $userInfo['user_id']; 1223 // $mailer->AddCustomHeader("X-e107-id: {$userInfo['user_id']}"); 1224 } 1225 1226 1227 if(getperms('0') && E107_DEBUG_LEVEL > 0) 1228 { 1229 e107::getMessage()->addDebug("Email Debugger active. <b>Simulation Only!</b>"); 1230 e107::getMessage()->addDebug($mailer->preview($eml)); 1231 return true; 1232 } 1233 1234 if(!empty($options['debug'])) 1235 { 1236 return $mailer->preview($eml); 1237 } 1238 1239 1240 return $mailer->sendEmail($userInfo['user_email'], $userInfo['user_name'], $eml, false); 1241 } 1242 1243 /** 1244 * Render user email. 1245 * Additional user fields: 1246 * 'mail_subject' -> required when type is not signup 1247 * 'mail_body' -> required when type is not signup 1248 * 'mail_copy_to' -> optional, carbon copy, used when type is not signup 1249 * 'mail_bcopy_to' -> optional, blind carbon copy, used when type is not signup 1250 * 'mail_attach' -> optional, attach files, available for all types, additionally it overrides $SIGNUPEMAIL_ATTACHMENTS when type is signup 1251 * 'mail_options' -> optional, available for all types, any additional valid mailer option as described in e107Email::sendEmail() phpDoc help (options above can override them) 1252 * All standard user fields from the DB (user_name, user_loginname, etc.) 1253 * 1254 * @param string $type signup|notify|email|quickadd 1255 * @param array $userInfo 1256 * @return array 1257 */ 1258 public function renderEmail($type, $userInfo) 1259 { 1260 global $SIGNUPEMAIL_USETHEME, $QUICKADDUSER_TEMPLATE, $NOTIFY_TEMPLATE; 1261 $pref = e107::getPref(); 1262 $ret = array(); 1263 $tp = e107::getParser(); 1264 $mes = e107::getMessage(); 1265 1266 1267 // mailer options 1268 if(isset($userInfo['mail_options']) && is_array($userInfo['mail_options'])) 1269 { 1270 $ret = $userInfo['mail_options']; 1271 } 1272 1273 // required for signup and quickadd email type 1274 e107::coreLan('signup'); 1275 1276 $EMAIL_TEMPLATE = e107::getCoreTemplate('email'); 1277 1278 if(!is_array($EMAIL_TEMPLATE)) //BC Fixes. pre v2 alpha3. 1279 { 1280 // load from old location. (root of theme folder if it exists) 1281 1282 $SIGNUPEMAIL_SUBJECT = ''; 1283 $SIGNUPEMAIL_CC = ''; 1284 $SIGNUPEMAIL_BCC = ''; 1285 $SIGNUPEMAIL_ATTACHMENTS = ''; 1286 $SIGNUPEMAIL_TEMPLATE = ''; 1287 1288 1289 if (file_exists(THEME.'email_template.php')) 1290 { 1291 include(THEME.'email_template.php'); 1292 } 1293 else 1294 { 1295 // include core default. 1296 include(e107::coreTemplatePath('email')); 1297 } 1298 1299 // BC Fixes. 1300 $EMAIL_TEMPLATE['signup']['subject'] = $SIGNUPEMAIL_SUBJECT; 1301 $EMAIL_TEMPLATE['signup']['cc'] = $SIGNUPEMAIL_CC; 1302 $EMAIL_TEMPLATE['signup']['bcc'] = $SIGNUPEMAIL_BCC; 1303 $EMAIL_TEMPLATE['signup']['attachments'] = $SIGNUPEMAIL_ATTACHMENTS; 1304 $EMAIL_TEMPLATE['signup']['body'] = $SIGNUPEMAIL_TEMPLATE; 1305 1306 $EMAIL_TEMPLATE['quickadduser']['body'] = vartrue($QUICKADDUSER_TEMPLATE['email_body'], ''); 1307 $EMAIL_TEMPLATE['notify']['body'] = vartrue($NOTIFY_TEMPLATE['email_body'], ''); 1308 1309 } 1310 1311 $template = ''; 1312 switch ($type) 1313 { 1314 case 'signup': 1315 $template = (vartrue($SIGNUPPROVIDEREMAIL_TEMPLATE)) ? $SIGNUPPROVIDEREMAIL_TEMPLATE : $EMAIL_TEMPLATE['signup']['body']; 1316 $ret['template'] = 'signup'; // // false Don't allow additional headers (mailer) ?? 1317 break; 1318 1319 case 'quickadd': 1320 $template = $EMAIL_TEMPLATE['quickadduser']['body']; 1321 $ret['template'] = 'quickadduser'; // Don't allow additional headers (mailer) 1322 break; 1323 1324 case 'notify': 1325 if(vartrue($userInfo['mail_body'])) $template = $userInfo['mail_body']; //$NOTIFY_HEADER.$userInfo['mail_body'].$NOTIFY_FOOTER; 1326 $ret['template'] = 'notify'; 1327 break; 1328 1329 case 'email': 1330 case 'default': 1331 if(vartrue($userInfo['mail_body'])) $template = $userInfo['mail_body']; //$EMAIL_HEADER.$userInfo['mail_body'].$EMAIL_FOOTER; 1332 $ret['template'] = 'default'; 1333 break; 1334 } 1335 1336 if(!$template) 1337 { 1338 $mes->addDebug('$template is empty in user_model.php line 1171.'); // Debug only, do not translate. 1339 return array(); 1340 } 1341 1342 1343 1344 // 1345 1346 // signup email only 1347 if($type == 'signup') 1348 { 1349 $HEAD = ''; 1350 $FOOT = ''; 1351 1352 $pass_show = e107::pref('core','user_reg_secureveri', false); 1353 1354 $ret['e107_header'] = $userInfo['user_id']; 1355 1356 if (vartrue($EMAIL_TEMPLATE['signup']['cc'])) { $ret['email_copy_to'] = $EMAIL_TEMPLATE['signup']['cc']; } 1357 if (vartrue($EMAIL_TEMPLATE['signup']['bcc'])) { $ret['email_bcopy_to'] = $EMAIL_TEMPLATE['signup']['bcc']; } 1358 if (vartrue($userInfo['email_attach'])) { $ret['email_attach'] = $userInfo['mail_attach']; } 1359 elseif (vartrue($EMAIL_TEMPLATE['signup']['attachments'])) { $ret['email_attach'] = $EMAIL_TEMPLATE['signup']['attachments']; } 1360 1361 $style = vartrue($SIGNUPEMAIL_LINKSTYLE) ? "style='{$SIGNUPEMAIL_LINKSTYLE}'" : ""; 1362 1363 1364 if(empty($userInfo['activation_url']) && !empty($userInfo['user_sess']) && !empty($userInfo['user_id'])) 1365 { 1366 $userInfo['activation_url'] = SITEURL."signup.php?activate.".$userInfo['user_id'].".".$userInfo['user_sess']; 1367 } 1368 1369 1370 $sc = array(); 1371 1372 $sc['LOGINNAME'] = intval($pref['allowEmailLogin']) === 0 ? $userInfo['user_loginname'] : $userInfo['user_email']; 1373 $sc['PASSWORD'] = ($pass_show && !empty($userInfo['user_password'])) ? '*************' : $userInfo['user_password']; 1374 $sc['ACTIVATION_LINK'] = strpos($userInfo['activation_url'], 'http') === 0 ? '<a href="'.$userInfo['activation_url'].'">'.$userInfo['activation_url'].'</a>' : $userInfo['activation_url']; 1375 // $sc['SITENAME'] = SITENAME; 1376 $sc['SITEURL'] = "<a href='".SITEURL."' {$style}>".SITEURL."</a>"; 1377 $sc['USERNAME'] = $userInfo['user_name']; 1378 $sc['USERURL'] = vartrue($userInfo['user_website']) ? $userInfo['user_website'] : ""; 1379 $sc['DISPLAYNAME'] = $userInfo['user_login'] ? $userInfo['user_login'] : $userInfo['user_name']; 1380 $sc['EMAIL'] = $userInfo['user_email']; 1381 $sc['ACTIVATION_URL'] = $userInfo['activation_url']; 1382 1383 $ret['subject'] = $EMAIL_TEMPLATE['signup']['subject']; // $subject; 1384 $ret['send_html'] = TRUE; 1385 $ret['shortcodes'] = $sc; 1386 1387 if(!varset($EMAIL_TEMPLATE['signup']['header'])) 1388 { 1389 1390 $HEAD = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"; 1391 $HEAD .= "<html xmlns='http://www.w3.org/1999/xhtml' >\n"; 1392 $HEAD .= "<head><meta http-equiv='content-type' content='text/html; charset=utf-8' />\n"; 1393 $HEAD .= ($SIGNUPEMAIL_USETHEME == 1) ? "<link rel=\"stylesheet\" href=\"".SITEURLBASE.THEME_ABS."style.css\" type=\"text/css\" />\n" : ""; 1394 $HEAD .= "<title>".LAN_SIGNUP_58."</title>\n"; 1395 1396 if($SIGNUPEMAIL_USETHEME == 2) // @deprecated in favor of {STYLESHEET} 1397 { 1398 $CSS = file_get_contents(THEME."style.css"); 1399 $HEAD .= "<style>\n".$CSS."\n</style>"; 1400 } 1401 1402 $HEAD .= "</head>\n"; 1403 if(vartrue($SIGNUPEMAIL_BACKGROUNDIMAGE)) // @deprecated. 1404 { 1405 $HEAD .= "<body background=\"".$SIGNUPEMAIL_BACKGROUNDIMAGE."\" >\n"; 1406 } 1407 else 1408 { 1409 $HEAD .= "<body>\n"; 1410 } 1411 1412 } 1413 else 1414 { 1415 $HEAD = ""; // $tp->parseTemplate($EMAIL_TEMPLATE['signup']['header'], true); 1416 } 1417 1418 if(!varset($EMAIL_TEMPLATE['signup']['footer'])) 1419 { 1420 $FOOT = "\n</body>\n</html>\n"; 1421 } 1422 else 1423 { 1424 $FOOT = ""; // $tp->parseTemplate($EMAIL_TEMPLATE['signup']['footer'], true); 1425 } 1426 1427 $ret['send_html'] = TRUE; 1428 $ret['email_body'] = $HEAD.$template.$FOOT; // e107::getParser()->parseTemplate(str_replace($search,$replace,$HEAD.$template.$FOOT), true); 1429 $ret['preview'] = $tp->parseTemplate($ret['email_body'],true, $sc);// Non-standard field 1430 $ret['shortcodes'] = $sc; 1431 1432 1433 return $ret; 1434 } 1435 1436 1437 1438 1439 // all other email types 1440 if(!$userInfo['mail_subject']) 1441 { 1442 $mes->addDebug('No Email subject provided to renderEmail() method.'); // Debug only, do not translate. 1443 return array(); 1444 } 1445 1446 1447 $templateName = $ret['template']; 1448 1449// $ret['email_subject'] = varset($EMAIL_TEMPLATE[$templateName]['subject'], $EMAIL_TEMPLATE['default']['subject']) ; // $subject; 1450 $ret['subject'] = $userInfo['mail_subject']; 1451 $ret['e107_header'] = $userInfo['user_id']; 1452 1453 if (vartrue($userInfo['email_copy_to'])) { $ret['email_copy_to'] = $userInfo['email_copy_to']; } 1454 if (vartrue($userInfo['email_bcopy_to'])) { $ret['email_bcopy_to'] = $userInfo['email_bcopy_to']; } 1455 if (vartrue($userInfo['email_attach'])) { $ret['email_attach'] = $userInfo['email_attach']; } 1456 1457 $sc = array(); 1458 1459 $sc['LOGINNAME'] = intval($pref['allowEmailLogin']) === 0 ? $userInfo['user_loginname'] : $userInfo['user_email']; 1460 $sc['DISPLAYNAME'] = $userInfo['user_login'] ? $userInfo['user_login'] : $userInfo['user_name']; 1461 $sc['SITEURL'] = "<a href='".SITEURL."'>".SITEURL."</a>"; 1462 $sc['USERNAME'] = $userInfo['user_name']; 1463 $sc['USERURL'] = vartrue($userInfo['user_website'], ''); 1464 $sc['PASSWORD'] = vartrue($userInfo['user_password'], '***********'); 1465 $sc['SUBJECT'] = $userInfo['mail_subject']; 1466 1467 1468 if(isset($userInfo['activation_url'])) 1469 { 1470 $sc['ACTIVATION_URL'] = $userInfo['activation_url']; 1471 $sc['ACTIVATION_LINK'] = strpos($userInfo['activation_url'], 'http') === 0 ? '<a href="'.$userInfo['activation_url'].'">'.$userInfo['activation_url'].'</a>' : $userInfo['activation_url']; 1472 } 1473 1474 $ret['send_html'] = true; 1475 $ret['email_body'] = $template; // e107::getParser()->parseTemplate(str_replace($search, $replace, $template)); - performed in mail handler. 1476 $ret['preview'] = $ret['mail_body']; // Non-standard field 1477 $ret['shortcodes'] = $sc; 1478 1479 return $ret; 1480 } 1481} 1482 1483/** 1484 * Current system user 1485 * @author SecretR 1486 */ 1487class e_user extends e_user_model 1488{ 1489 private $_session_data = null; 1490 private $_session_key = null; 1491 private $_session_type = null; 1492 private $_session_error = false; 1493 1494 private $_parent_id = false; 1495 private $_parent_data = array(); 1496 private $_parent_extmodel = null; 1497 private $_parent_extstruct = null; 1498 private $_parent_config = null; 1499 1500 /** 1501 * @var e_user_provider|null 1502 */ 1503 protected $_provider; 1504 1505 public function __construct() 1506 { 1507 parent::__construct(); 1508 $this->setSessionData() // retrieve data from current session 1509 ->load() // load current user from DB 1510 ->setEditor($this); // reference to self 1511 } 1512 1513 /** 1514 * Yes, it's current user - return always true 1515 * NOTE: it's not user check, use isUser() instead! 1516 * @return boolean 1517 */ 1518 final public function isCurrent() 1519 { 1520 return true; 1521 } 1522 1523 /** 1524 * Get parent user ID - present if main admin is browsing 1525 * front-end logged in as another user account 1526 * 1527 * @return integer or false if not present 1528 */ 1529 final public function getParentId() 1530 { 1531 return $this->_parent_id; 1532 } 1533 1534 /** 1535 * Init external user login/signup provider 1536 * @return e_user 1537 */ 1538 public function initProvider() 1539 { 1540 if(null !== $this->_provider) return $this; 1541 1542 if($this->get('user_xup')) 1543 { 1544 $providerId = $this->getProviderName(); 1545 $this->_provider = e107::getUserProvider($providerId); 1546 } 1547 1548 return $this; 1549 } 1550 1551 /** 1552 * Get external user provider 1553 * @return e_user_provider|null 1554 */ 1555 public function getProvider() 1556 { 1557 if(null === $this->_provider) $this->initProvider(); 1558 return $this->_provider; 1559 } 1560 1561 1562 /** 1563 * Set external user provider (already initialized) 1564 * @return e_user 1565 */ 1566 public function setProvider($provider) 1567 { 1568 $this->_provider = $provider; 1569 return $this; 1570 } 1571 1572 /** 1573 * Check if this user has assigned login provider 1574 * @return boolean 1575 */ 1576 public function hasProvider() 1577 { 1578 return ($this->getProvider() !== null); 1579 } 1580 1581 /** 1582 * User login 1583 * @param string $uname 1584 * @param string $upass_plain 1585 * @param boolean $uauto 1586 * @param string $uchallange 1587 * @param boolean $noredirect 1588 * @return boolean success 1589 */ 1590 final public function login($uname, $upass_plain, $uauto = false, $uchallange = false, $noredirect = true) 1591 { 1592 if($this->isUser()) return false; 1593 1594 $userlogin = new userlogin(); 1595 $loginSuccess = $userlogin->login($uname, $upass_plain, $uauto, $uchallange, $noredirect); 1596 1597 $userdata = $userlogin->getUserData(); 1598 $this->setSessionData(true)->setData($userdata); 1599 if ($loginSuccess === false) return false; 1600 1601 e107::getEvent()->trigger('user_login', $userdata); 1602 1603 return $this->isUser(); 1604 } 1605 1606 /** 1607 * User login via external user provider 1608 * @param string $xup external user provider identifier 1609 * @return boolean success 1610 */ 1611 final public function loginProvider($xup) 1612 { 1613 if(!e107::getUserProvider()->isSocialLoginEnabled()) return false; 1614 1615 if($this->isUser()) return true; 1616 1617 $userlogin = new userlogin(); 1618 $userlogin->login($xup, '', 'provider', false, true); 1619 1620 $userdata = $userlogin->getUserData(); 1621 1622 if(defset('E107_DEBUG_LEVEL', 0) > 0) 1623 { 1624 e107::getLog()->add('XUP Debug', (__CLASS__ . ':' . __METHOD__ . '-' . __LINE__), E_LOG_INFORMATIVE, "XUP_DEBUG"); 1625 } 1626 1627 $this->setSessionData(true)->setData($userdata); 1628 1629 e107::getEvent()->trigger('user_xup_login', $userdata); 1630 1631 return $this->isUser(); 1632 } 1633 1634 /** 1635 * Login as another user account 1636 * @param integer $user_id 1637 * @return boolean success 1638 */ 1639 final public function loginAs($user_id) 1640 { 1641 // TODO - set session data required for loadAs() 1642 if($this->getParentId() 1643 || !$this->isMainAdmin() 1644 || empty($user_id) 1645 || $this->getSessionDataAs() 1646 || $user_id == $this->getId() 1647 ) return false; 1648 1649 $key = $this->_session_key.'_as'; 1650 1651 if('session' == $this->_session_type) 1652 { 1653 $_SESSION[$key] = $user_id; 1654 } 1655 elseif('cookie' == $this->_session_type) 1656 { 1657 $_COOKIE[$key] = $user_id; 1658 cookie($key, $user_id); 1659 } 1660 1661 // TODO - lan 1662 e107::getAdminLog()->log_event('Head Admin used Login As feature', 'Head Admin [#'.$this->getId().'] '.$this->getName().' logged in user account #'.$user_id); 1663 //$this->loadAs(); - shouldn't be called here - loginAs should be called in Admin area only, loadAs - front-end 1664 return true; 1665 } 1666 1667 /** 1668 * 1669 * @return e_user 1670 */ 1671 protected function _initConstants() 1672 { 1673 //FIXME - BC - constants from init_session() should be defined here 1674 // [SecretR] Not sure we should do this here, it's too restricting - constants can be 1675 // defined once, we need the freedom to do it multiple times - e.g. load() executed in constructor than login(), loginAs() etc. 1676 // called by a controller 1677 // We should switch to e.g. isAdmin() instead of ADMIN constant check 1678 return $this; 1679 } 1680 1681 /** 1682 * Destroy cookie/session data, self destroy 1683 * @return e_user 1684 */ 1685 final public function logout() 1686 { 1687 if($this->hasProvider()) 1688 { 1689 $this->getProvider()->logout(); 1690 } 1691 $this->logoutAs() 1692 ->_destroySession(); 1693 1694 parent::destroy(); 1695 //if(session_id()) session_destroy(); 1696 e107::getSession()->destroy(); 1697 1698 e107::setRegistry('core/e107/current_user', null); 1699 return $this; 1700 } 1701 1702 /** 1703 * Destroy cookie/session/model data for current user, resurrect parent user 1704 * @return e_user 1705 */ 1706 final public function logoutAs() 1707 { 1708 if($this->getParentId()) 1709 { 1710 // load parent user data 1711 $this->_extended_model = $this->_parent_extmodel; 1712 $this->_extended_structure = $this->_parent_extstruct; 1713 $this->_user_config = $this->_parent_config; 1714 if($this->_parent_model) 1715 $this->setData($this->_parent_model->getData()); 1716 1717 // cleanup 1718 $this->_parent_id = false; 1719 $this->_parent_model = $this->_parent_extstruct = $this->_parent_extmodel = $this->_parent_config = null; 1720 } 1721 $this->_destroyAsSession(); 1722 return $this; 1723 } 1724 1725 /** 1726 * TODO load user data by cookie/session data 1727 * @return e_user 1728 */ 1729 final public function load($force = false, $denyAs = false) 1730 { 1731 if(!$force && $this->getId()) return $this; 1732 1733 if(deftrue('e_ADMIN_AREA')) $denyAs = true; 1734 1735 // always run cli as main admin 1736 if(e107::isCli()) 1737 { 1738 $this->_load(1, $force); 1739 $this->_initConstants(); 1740 return $this; 1741 } 1742 1743 // We have active session 1744 if(null !== $this->_session_data) 1745 { 1746 list($uid, $upw) = explode('.', $this->_session_data); 1747 // Bad cookie - destroy session 1748 if(empty($uid) || !is_numeric($uid) || empty($upw)) 1749 { 1750 $this->_destroyBadSession(); 1751 $this->_initConstants(); 1752 return $this; 1753 } 1754 1755 $udata = $this->_load($uid, $force); 1756 // Bad cookie - destroy session 1757 if(empty($udata)) 1758 { 1759 $this->_destroyBadSession(); 1760 $this->_initConstants(); 1761 return $this; 1762 } 1763 1764 // we have a match 1765 if(md5($udata['user_password']) == $upw) 1766 { 1767 // set current user data 1768 $this->setData($udata); 1769 1770 // NEW - try 'logged in as' feature 1771 if(!$denyAs) $this->loadAs(); 1772 1773 // update lastvisit field 1774 $this->updateVisit(); 1775 1776 // currently does nothing 1777 $this->_initConstants(); 1778 1779 // init any available external user provider 1780 if(e107::getUserProvider()->isSocialLoginEnabled()) $this->initProvider(); 1781 1782 return $this; 1783 } 1784 1785 $this->_destroyBadSession(); 1786 $this->_initConstants(); 1787 return $this; 1788 } 1789 1790 return $this; 1791 } 1792 1793 final public function loadAs() 1794 { 1795 // FIXME - option to avoid it when browsing Admin area 1796 $loginAs = $this->getSessionDataAs(); 1797 if(!$this->getParentId() && false !== $loginAs && $loginAs !== $this->getId() && $loginAs !== 1 && $this->isMainAdmin()) 1798 { 1799 $uasdata = $this->_load($loginAs); 1800 if(!empty($uasdata)) 1801 { 1802 // backup parent user data to prevent further db queries 1803 $this->_parent_id = $this->getId(); 1804 $this->_parent_model = new e_user_model($this->getData()); 1805 $this->setData($uasdata); 1806 1807 // not allowed - revert back 1808 if($this->isMainAdmin()) 1809 { 1810 $this->_parent_id = false; 1811 $this->setData($this->_parent_model->getData()); 1812 $this->_parent_model = null; 1813 $this->_destroyAsSession(); 1814 } 1815 else 1816 { 1817 $this->_parent_extmodel = $this->_extended_model; 1818 $this->_parent_extstruct = $this->_extended_structure; 1819 $this->_user_config = $this->_parent_config; 1820 $this->_extended_model = $this->_extended_structure = $this->_user_config = null; 1821 } 1822 } 1823 } 1824 else 1825 { 1826 $this->_parent_id = false; 1827 $this->_parent_model = null; 1828 $this->_parent_extstruct = $this->_parent_extmodel = null; 1829 } 1830 return $this; 1831 } 1832 1833 /** 1834 * Update user visit timestamp 1835 * @return void 1836 */ 1837 protected function updateVisit() 1838 { 1839 // Don't update if main admin is logged in as current (non main admin) user 1840 if(!$this->getParentId()) 1841 { 1842 $sql = e107::getDb(); 1843 $this->set('last_ip', $this->get('user_ip')); 1844 $current_ip = e107::getIPHandler()->getIP(FALSE); 1845 $update_ip = $this->get('user_ip' != $current_ip ? ", user_ip = '".$current_ip."'" : ""); 1846 $this->set('user_ip', $current_ip); 1847 if($this->get('user_currentvisit') + 3600 < time() || !$this->get('user_lastvisit')) 1848 { 1849 $this->set('user_lastvisit', (integer) $this->get('user_currentvisit')); 1850 $this->set('user_currentvisit', time()); 1851 $sql->update('user', "user_visits = user_visits + 1, user_lastvisit = ".$this->get('user_lastvisit').", user_currentvisit = ".$this->get('user_currentvisit')."{$update_ip} WHERE user_id='".$this->getId()."' "); 1852 } 1853 else 1854 { 1855 $this->set('user_currentvisit', time()); 1856 $sql->update('user', "user_currentvisit = ".$this->get('user_currentvisit')."{$update_ip} WHERE user_id='".$this->getId()."' "); 1857 } 1858 } 1859 } 1860 1861 final protected function _destroySession() 1862 { 1863 cookie($this->_session_key, '', (time() - 2592000)); 1864 unset($_SESSION[$this->_session_key]); 1865 1866 return $this; 1867 } 1868 1869 final protected function _destroyAsSession() 1870 { 1871 $key = $this->_session_key.'_as'; 1872 cookie($key, '', (time() - 2592000)); 1873 $_SESSION[$key] = ''; 1874 unset($_SESSION[$key]); 1875 1876 return $this; 1877 } 1878 1879 final protected function _destroyBadSession() 1880 { 1881 $this->_session_error = true; 1882 return $this->_destroySession(); 1883 } 1884 1885 final public function getSessionDataAs() 1886 { 1887 $id = false; 1888 $key = $this->_session_key.'_as'; 1889 1890 if('session' == $this->_session_type && isset($_SESSION[$key]) && !empty($_SESSION[$key])) 1891 { 1892 $id = $_SESSION[$key]; 1893 } 1894 elseif('cookie' == $this->_session_type && isset($_COOKIE[$key]) && !empty($_COOKIE[$key])) 1895 { 1896 $id = $_COOKIE[$key]; 1897 } 1898 1899 if(!empty($id) && is_numeric($id)) return intval($id); 1900 1901 return false; 1902 } 1903 1904 final public function setSessionData($force = false) 1905 { 1906 if($force || null === $this->_session_data) 1907 { 1908 $this->_session_data = null; 1909 $this->_session_key = e107::getPref('cookie_name', 'e107cookie'); 1910 $this->_session_type = e107::getPref('user_tracking', 'cookie'); 1911 1912 if('session' == $this->_session_type && isset($_SESSION[$this->_session_key]) && !empty($_SESSION[$this->_session_key])) 1913 { 1914 $this->_session_data = &$_SESSION[$this->_session_key]; 1915 } 1916 elseif('cookie' == $this->_session_type && isset($_COOKIE[$this->_session_key]) && !empty($_COOKIE[$this->_session_key])) 1917 { 1918 $this->_session_data = &$_COOKIE[$this->_session_key]; 1919 } 1920 } 1921 1922 return $this; 1923 } 1924 1925 public function hasSessionError() 1926 { 1927 return $this->_session_error; 1928 } 1929 1930 1931 final protected function _load($user_id) 1932 { 1933 $qry = 'SELECT u.*, ue.* FROM #user AS u LEFT JOIN #user_extended as ue ON u.user_id=ue.user_extended_id WHERE u.user_id='.intval($user_id); 1934 if(e107::getDb()->gen($qry)) 1935 { 1936 return e107::getDb()->fetch(); 1937 } 1938 return array(); 1939 } 1940 1941 /** 1942 * Not allowed 1943 * 1944 * @return e_user_model 1945 */ 1946 final protected function setAsTarget() 1947 { 1948 return $this; 1949 } 1950 1951 /** 1952 * Not allowed 1953 * 1954 * @return e_user_model 1955 */ 1956 final protected function clearTarget() 1957 { 1958 return $this; 1959 } 1960 1961 public function destroy() 1962 { 1963 // not allowed - see logout() 1964 } 1965} 1966 1967class e_user_extended_model extends e_admin_model 1968{ 1969 /** 1970 * Describes known model fields 1971 * @var array 1972 */ 1973 protected $_data_fields = array( 1974 'user_extended_id' => 'integer', 1975 'user_hidden_fields' => 'string', 1976 ); 1977 1978 /** 1979 * @see e_model 1980 * @var string 1981 */ 1982 protected $_db_table = 'user_extended'; 1983 1984 /** 1985 * @see e_model 1986 * @var string 1987 */ 1988 protected $_field_id = 'user_extended_id'; 1989 1990 /** 1991 * @see e_model 1992 * @var string 1993 */ 1994 protected $_message_stack = 'user'; 1995 1996 /** 1997 * User class as set in user Adminsitration 1998 * 1999 * @var integer 2000 */ 2001 protected $_memberlist_access = null; 2002 2003 /** 2004 * @var e_user_extended_structure_tree 2005 */ 2006 protected $_structure = null; 2007 2008 /** 2009 * User model, the owner of extended fields model 2010 * @var e_user_model 2011 */ 2012 protected $_user = null; 2013 2014 /** 2015 * Stores access classes and default value per custom field 2016 * @var array 2017 */ 2018 protected $_struct_index = array(); 2019 2020 /** 2021 * Constructor 2022 * @param e_user_model $user_model 2023 * @return void 2024 */ 2025 public function __construct(e_user_model $user_model) 2026 { 2027 $this->_memberlist_access = e107::getPref('memberlist_access'); 2028 $this->setUser($user_model) 2029 ->load(); 2030 } 2031 2032 /** 2033 * Always return integer 2034 */ 2035 public function getId() 2036 { 2037 return (integer) parent::getId(); 2038 } 2039 2040 /** 2041 * Get user model 2042 * @return e_user_model 2043 */ 2044 public function getUser() 2045 { 2046 return $this->_user; 2047 } 2048 2049 /** 2050 * Set User model 2051 * @param e_user_model $user_model 2052 * @return e_user_extended_model 2053 */ 2054 public function setUser($user_model) 2055 { 2056 $this->_user = $user_model; 2057 return $this; 2058 } 2059 2060 /** 2061 * Get current user editor model 2062 * @return e_user_model 2063 */ 2064 public function getEditor() 2065 { 2066 return $this->getUser()->getEditor(); 2067 } 2068 2069 /** 2070 * Bad but required (BC) method of retrieving all user data 2071 * It's here to be used from get_user_data() core function. 2072 * DON'T USE IT unless you have VERY good reason to do it. 2073 * TODO - revise this! Merge it to getSystemData, getApplicableData 2074 * 2075 * @return array 2076 */ 2077 public function getExtendedData() 2078 { 2079 $ret = array(); 2080 2081 $fields = $this->getExtendedStructure()->getFieldTree(); 2082 foreach ($fields as $id => $field) 2083 { 2084 $value = $this->getValue($field->getValue('name')); 2085 if(null !== $value) $ret[$field->getValue('name')] = $value; 2086 } 2087 2088 $ret['user_extended_id'] = $this->getId(); 2089 $ret['user_hidden_fields'] = $this->get('user_hidden_fields'); 2090 2091 return $ret; 2092 } 2093 2094 /** 2095 * Get User extended field value. It performs all required read/applicable permission checks 2096 * against current editor/user. 2097 * Returns NULL when field/default value not found or not enough permissions 2098 * @param string $field 2099 * @param boolean $short if true, 'user_' prefix will be added to field name 2100 * @param boolean $raw doesn't retrieve db value when true (no sql query) 2101 * @return mixed 2102 */ 2103 public function getValue($field, $short = true, $raw = false) 2104 { 2105 if($short) $field = 'user_'.$field; 2106 if (!$this->checkRead($field)) 2107 return null; 2108 if(!$raw && vartrue($this->_struct_index[$field]['db'])) 2109 { 2110 return $this->getDbValue($field); 2111 } 2112 return $this->get($field, $this->getDefault($field)); 2113 } 2114 2115 /** 2116 * Set User extended field value, only if current editor has write permissions and field 2117 * is applicable for the current user. 2118 * Note: Data is not sanitized! 2119 * @param string $field 2120 * @param mixed $value 2121 * @param boolean $short if true, 'user_' prefix will be added to field name 2122 * @return e_user_extended_model 2123 */ 2124 public function setValue($field, $value, $short = true) 2125 { 2126 if($short) $field = 'user_'.$field; 2127 if (!$this->checkWrite($field)) 2128 return $this; 2129 2130 $this->set($field, $value, true); 2131 return $this; 2132 } 2133 2134 /** 2135 * Retrieve value of a field of type 'db'. It does sql request only once. 2136 * 2137 * @param string $field field name 2138 * @return mixed db value 2139 */ 2140 protected function getDbValue($field) 2141 { 2142 if(null !== $this->_struct_index[$field]['db_value']) 2143 { 2144 return $this->_struct_index[$field]['db_value']; 2145 } 2146 2147 // retrieve db data 2148 $value = $this->get($field); 2149 list($table, $field_id, $field_name, $field_order) = explode(',', $this->_struct_index[$field]['db'], 4); 2150 $this->_struct_index[$field]['db_value'] = $value; 2151 if($value && $table && $field_id && $field_name && e107::getDb()->db_Select($table, $field_name, "{$field_id}='{$value}'")) 2152 { 2153 $res = e107::getDb()->db_Fetch(); 2154 $this->_struct_index[$field]['db_value'] = $res[$field_name]; 2155 } 2156 2157 return $this->_struct_index[$field]['db_value']; 2158 } 2159 2160 /** 2161 * System getter. It doesn't perform any read/applicable permission checks 2162 * against current editor/user. 2163 * It's here to serve in your application logic. 2164 * 2165 * @param string $field 2166 * @param boolean $short if true, 'user_' prefix will be added to field name 2167 * @param boolean $raw don't retrieve db value 2168 * @return mixed 2169 */ 2170 public function getSystem($field, $short = true, $raw = true) 2171 { 2172 if($short) $field = 'user_'.$field; 2173 2174 if(!$raw && vartrue($this->_struct_index[$field]['db'])) 2175 { 2176 return $this->getDbValue($field); 2177 } 2178 return $this->get($field, $this->getDefault($field)); 2179 } 2180 2181 /** 2182 * System setter. It doesn't perform any write/applicable permission checks 2183 * against current editor/user. 2184 * It's here to serve in your application logic. 2185 * NOTE: untrusted data should be provided via setPosted() method! 2186 * 2187 * @param string $field 2188 * @param mixed $value 2189 * @param boolean $short if true, 'user_' prefix will be added to field name 2190 * @param boolean $strict if false no Applicable check will be made 2191 * @return e_user_model 2192 */ 2193 public function setSystem($field, $value, $short = true, $strict = true) 2194 { 2195 if($short) $field = 'user_'.$field; 2196 2197 $this->set($field, $value, $strict); 2198 return $this; 2199 } 2200 2201 public function getReadData() 2202 { 2203 // TODO array allowed user profile page data (read mode) 2204 } 2205 2206 public function getWriteData() 2207 { 2208 // TODO array allowed user settings page data (edit mode) 2209 } 2210 2211 /** 2212 * Get default field value, defined by extended field structure 2213 * Returns NULL if field/default value not found 2214 * @param string $field 2215 * @return mixed 2216 */ 2217 public function getDefault($field) 2218 { 2219 return varset($this->_struct_index[$field]['default'], null); 2220 } 2221 2222 /** 2223 * Check field read permissions against current editor 2224 * @param string $field 2225 * @return boolean 2226 */ 2227 public function checkRead($field) 2228 { 2229 $hidden = $this->get('user_hidden_fields'); 2230 $editor = $this->getEditor(); 2231 2232 if(!empty($hidden) && $this->getId() !== $editor->getId() && strpos($hidden, '^'.$field.'^') !== false) return false; 2233 2234 return ($this->checkApplicable($field) && $editor->checkClass($this->_memberlist_access) && $editor->checkClass(varset($this->_struct_index[$field]['read']))); 2235 } 2236 2237 /** 2238 * Check field write permissions against current editor 2239 * @param string $field 2240 * @return boolean 2241 */ 2242 public function checkWrite($field) 2243 { 2244 if(!$this->checkApplicable($field)) return false; 2245 2246 $editor = $this->getEditor(); 2247 // Main admin checked later in checkClass() method 2248 if($editor->checkAdminPerms('4') && varset($this->_struct_index[$field]['write']) != e_UC_NOBODY) 2249 return true; 2250 2251 return $editor->checkClass(varset($this->_struct_index[$field]['write'])); 2252 } 2253 2254 /** 2255 * Check field signup permissions 2256 * @param string $field 2257 * @return boolean 2258 */ 2259 public function checkSignup($field) 2260 { 2261 return $this->getUser()->checkClass(varset($this->_struct_index[$field]['signup'])); 2262 } 2263 2264 /** 2265 * Check field applicable permissions against current user 2266 * @param string $field 2267 * @return boolean 2268 */ 2269 public function checkApplicable($field) 2270 { 2271 return $this->getUser()->checkClass(varset($this->_struct_index[$field]['apply'])); 2272 } 2273 2274 /** 2275 * @see e_model#load($id, $force) 2276 * @return e_user_extended_model 2277 */ 2278 public function load($id=null, $force = false) 2279 { 2280 if ($this->getId() && !$force) 2281 return $this; 2282 2283 $this->_loadDataAndAccess(); 2284 return $this; 2285 } 2286 2287 /** 2288 * Check if given field name is present in extended user table structure 2289 * 2290 * @param string $field 2291 * @param boolean $short 2292 * @return boolean 2293 */ 2294 public function isField($field, $short = true) 2295 { 2296 if($short) $field = 'user_'.$field; 2297 return (isset($this->_struct_index[$field]) || in_array($field, array($this->getFieldIdName(), 'user_hidden_fields'))); 2298 } 2299 2300 /** 2301 * Load extended fields permissions once (performance) 2302 * @return e_user_extended_model 2303 */ 2304 protected function _loadDataAndAccess() 2305 { 2306 $struct_tree = $this->getExtendedStructure(); 2307 $user = $this->getUser(); 2308 if ($user && $struct_tree->hasTree()) 2309 { 2310 // load structure dependencies 2311 $ignore = array($this->getFieldIdName(), 'user_hidden_fields'); 2312 2313 // set ignored values 2314 foreach ($ignore as $field_name) 2315 { 2316 $this->set($field_name, $user->get($field_name)); 2317 } 2318 2319 $fields = $struct_tree->getTree(); 2320 foreach ($fields as $id => $field) 2321 { 2322 $field_name = 'user_'.$field->getValue('name'); 2323 $this->set($field_name, $user->get($field_name)); 2324 if (!in_array($field->getValue('name'), $ignore)) 2325 { 2326 $this->_struct_index[$field_name] = array( 2327 'db' => $field->getValue('type') == 4 ? $field->getValue('values') : '', 2328 'db_value' => null, // used later for caching DB results 2329 'read' => $field->getValue('read'), 2330 'write' => $field->getValue('write'), 2331 'signup' => $field->getValue('signup'), 2332 'apply' => $field->getValue('applicable'), 2333 'default' => $field->getValue('default'), 2334 ); 2335 } 2336 } 2337 } 2338 return $this; 2339 } 2340 2341 /** 2342 * Build manage rules for single field 2343 * @param $structure_model 2344 * @return e_user_extended_model 2345 */ 2346 protected function _buildManageField(e_user_extended_structure_model $structure_model) 2347 { 2348 $ftype = $structure_model->getValue('type') == 6 ? 'integer' : 'string'; 2349 2350 // 0- field control (html) attributes;1 - regex; 2 - validation error msg; 2351 $parms = explode('^,^', $structure_model->getValue('parms')); 2352 2353 // validaton rules 2354 $vtype = $parms[1] ? 'regex' : $ftype; 2355 $name = 'user_'.$structure_model->getValue('name'); 2356 $this->setValidationRule($name, array($vtype, $parms[1], $structure_model->getValue('text'), $parms[2]), $structure_model->getValue('required')); 2357 2358 // data type, required for sql query 2359 $this->_data_fields[$name] = $ftype; 2360 return $this; 2361 } 2362 2363 /** 2364 * Build manage rules for single field 2365 * @param $structure_model 2366 * @return e_user_extended_model 2367 */ 2368 protected function _buildManageRules() 2369 { 2370 $struct_tree = $this->getExtendedStructure(); 2371 if ($this->getId() && $struct_tree->hasTree()) 2372 { 2373 // load structure dependencies TODO protected fields check as method 2374 $ignore = array($this->getFieldIdName(), 'user_hidden_fields'); // TODO - user_hidden_fields? Old? 2375 $fields = $struct_tree->getTree(); 2376 foreach ($fields as $id => $field) 2377 { 2378 if (!in_array('user_'.$field->getValue('name'), $ignore) && !$field->isCategory()) 2379 { 2380 // build _data_type and rules 2381 $this->_buildManageField($field); 2382 } 2383 } 2384 } 2385 return $this; 2386 } 2387 2388 /** 2389 * Get extended structure tree 2390 * @return e_user_extended_structure_tree 2391 */ 2392 public function getExtendedStructure() 2393 { 2394 if (null === $this->_structure) 2395 $this->_structure = e107::getUserStructure(); 2396 return $this->_structure; 2397 } 2398 2399 /** 2400 * Additional security while applying posted 2401 * data to user extended model 2402 * @return e_user_extended_model 2403 */ 2404 public function mergePostedData($strict = true, $sanitize = true, $validate = true) 2405 { 2406 $posted = $this->getPostedData(); 2407 foreach ($posted as $key => $value) 2408 { 2409 if(!$this->checkWrite($key)) 2410 { 2411 $this->removePosted($key); 2412 } 2413 } 2414 parent::mergePostedData(true, true, true); 2415 return $this; 2416 } 2417 2418 /** 2419 * Build data types and rules on the fly and save 2420 * @see e_front_model::save() 2421 */ 2422 public function save($from_post = true, $force = false, $session = false) 2423 { 2424 // when not loaded from db, see the construct check 2425 if(!$this->getId()) 2426 { 2427 $this->setId($this->getUser()->getId()); 2428 } 2429 $this->_buildManageRules(); 2430 // insert new record 2431 if(!e107::getDb()->db_Count('user_extended', '(user_extended_id)', "user_extended_id=".$this->getId())) 2432 { 2433 return $this->insert(true, $session); 2434 } 2435 return parent::save(true, $force, $session); 2436 } 2437 2438 /** 2439 * Doesn't save anything actually... 2440 */ 2441 public function saveDebug($return = false, $undo = true) 2442 { 2443 $this->_buildManageRules(); 2444 return parent::saveDebug($return, $undo); 2445 } 2446} 2447 2448class e_user_extended_structure_model extends e_model 2449{ 2450 /** 2451 * @see e_model 2452 * @var string 2453 */ 2454 protected $_db_table = 'user_extended_struct'; 2455 2456 /** 2457 * @see e_model 2458 * @var string 2459 */ 2460 protected $_field_id = 'user_extended_struct_id'; 2461 2462 /** 2463 * @see e_model 2464 * @var string 2465 */ 2466 protected $_message_stack = 'user_struct'; 2467 2468 /** 2469 * Get User extended structure field value 2470 * 2471 * @param string$field 2472 * @param string $default 2473 * @return mixed 2474 */ 2475 public function getValue($field, $default = '') 2476 { 2477 $field = 'user_extended_struct_'.$field; 2478 return $this->get($field, $default); 2479 } 2480 2481 /** 2482 * Set User extended structure field value 2483 * 2484 * @param string $field 2485 * @param mixed $value 2486 * @return e_user_model 2487 */ 2488 public function setValue($field, $value) 2489 { 2490 $field = 'user_extended_struct_'.$field; 2491 $this->set($field, $value, false); 2492 return $this; 2493 } 2494 2495 public function isCategory() 2496 { 2497 return ($this->getValue('type') ? false : true); 2498 } 2499 2500 public function getCategoryId() 2501 { 2502 return $this->getValue('parent'); 2503 } 2504 2505 public function getLabel() 2506 { 2507 $label = $this->isCategory() ? $this->getValue('name') : $this->getValue('text'); 2508 return defset($label, $label); 2509 } 2510 2511 /** 2512 * Loading of single structure row not allowed for front model 2513 */ 2514 public function load($id = null, $force = false) 2515 { 2516 return $this; 2517 } 2518} 2519 2520class e_user_extended_structure_tree extends e_tree_model 2521{ 2522 /** 2523 * @see e_model 2524 * @var string 2525 */ 2526 protected $_db_table = 'user_extended_struct'; 2527 2528 /** 2529 * @see e_model 2530 * @var string 2531 */ 2532 protected $_field_id = 'user_extended_struct_id'; 2533 2534 /** 2535 * @see e_model 2536 * @var string 2537 */ 2538 protected $_message_stack = 'user'; 2539 2540 /** 2541 * @var string 2542 */ 2543 protected $_cache_string = 'nomd5_user_extended_struct'; 2544 2545 /** 2546 * Force system cache (cache used even if disabled by site admin) 2547 * @var boolen 2548 */ 2549 protected $_cache_force = true; 2550 2551 /** 2552 * Index for speed up retrieving by name routine 2553 * @var array 2554 */ 2555 protected $_name_index = array(); 2556 2557 /** 2558 * Category Index - numerical array of id's 2559 * @var array 2560 */ 2561 protected $_category_index = array(); 2562 2563 /** 2564 * Items by category list 2565 * @var array 2566 */ 2567 protected $_parent_index = array(); 2568 2569 /** 2570 * Constructor - auto-load 2571 * @return void 2572 */ 2573 public function __construct() 2574 { 2575 $this->loadBatch(); 2576 } 2577 2578 /** 2579 * @param string $name name field value 2580 * @return e_user_extended_structure_model 2581 */ 2582 public function getNodeByName($name) 2583 { 2584 if ($this->isNodeName($name)) 2585 { 2586 return $this->getNode($this->getNodeId($name)); 2587 } 2588 return null; 2589 } 2590 2591 /** 2592 * Check if node exists by its name field value 2593 * @param string $name 2594 * @return boolean 2595 */ 2596 public function isNodeName($name) 2597 { 2598 return (isset($this->_name_index[$name]) && $this->isNode($this->_name_index[$name])); 2599 } 2600 2601 /** 2602 * Get node ID by node name field 2603 * @param string $name 2604 * @return integer 2605 */ 2606 public function getNodeId($name) 2607 { 2608 return (isset($this->_name_index[$name]) ? $this->_name_index[$name] : null); 2609 } 2610 2611 /** 2612 * Get collection of nodes of type category 2613 * @return array 2614 */ 2615 public function getCategoryTree() 2616 { 2617 return $this->_array_intersect_key($this->getTree(), array_combine($this->_category_index, $this->_category_index)); 2618 } 2619 2620 /** 2621 * Get collection of nodes of type field 2622 * @return array 2623 */ 2624 public function getFieldTree() 2625 { 2626 return array_diff_key($this->getTree(), array_combine($this->_category_index, $this->_category_index)); 2627 } 2628 2629 /** 2630 * Get collection of nodes assigned to a specific category 2631 * @param integer $category_id 2632 * @return array 2633 */ 2634 public function getTreeByCategory($category_id) 2635 { 2636 if(!isset($this->_parent_index[$category_id]) || empty($this->_parent_index[$category_id])) return array(); 2637 return $this->_array_intersect_key($this->getTree(), array_combine($this->_parent_index[$category_id], $this->_parent_index[$category_id])); 2638 } 2639 2640 /** 2641 * Load tree data 2642 * 2643 * @param boolean $force 2644 */ 2645 public function loadBatch($force = false) 2646 { 2647 $this->setParam('nocount', true) 2648 ->setParam('model_class', 'e_user_extended_structure_model') 2649 ->setParam('db_order', 'user_extended_struct_order ASC'); 2650 parent::loadBatch($force); 2651 2652 return $this; 2653 } 2654 2655 /** 2656 * Build all indexes on load 2657 * (New) This method is auto-triggered by core load() method 2658 * @param e_user_extended_structure_model $model 2659 */ 2660 protected function _onLoad($model) 2661 { 2662 if($model->isCategory()) 2663 { 2664 $this->_category_index[] = $model->getId(); 2665 } 2666 else 2667 { 2668 $this->_name_index['user_'.$model->getValue('name')] = $model->getId(); 2669 $this->_parent_index[$model->getCategoryId()][] = $model->getId(); 2670 } 2671 return $this; 2672 } 2673 2674 /** 2675 * Compatibility - array_intersect_key() available since PHP 5.1 2676 * 2677 * @see http://php.net/manual/en/function.array-intersect-key.php 2678 * @param array $array1 2679 * @param array $array2 2680 * @return array 2681 */ 2682 protected function _array_intersect_key($array1, $array2) 2683 { 2684 if(function_exists('array_intersect_key')) return array_intersect_key($array1, $array2); 2685 2686 $ret = array(); 2687 foreach ($array1 as $k => $v) 2688 { 2689 if(isset($array2[$k])) $ret[$k] = $v; 2690 } 2691 return $ret; 2692 } 2693} 2694 2695class e_user_pref extends e_front_model 2696{ 2697 /** 2698 * @var e_user_model 2699 */ 2700 protected $_user; 2701 2702 /** 2703 * Constructor 2704 * @param e_user_model $user_model 2705 * @return void 2706 */ 2707 public function __construct(e_user_model $user_model) 2708 { 2709 $this->_user = $user_model; 2710 $this->load(); 2711 } 2712 2713 /** 2714 * Load data from user preferences string 2715 * @param boolean $force 2716 * @return e_user_pref 2717 */ 2718 public function load($id = null, $force = false) 2719 { 2720 if($force || !$this->hasData()) 2721 { 2722 $data = $this->_user->get('user_prefs', ''); 2723 if(!empty($data)) 2724 { 2725 // BC 2726 $data = substr($data, 0, 5) == "array" ? e107::unserialize($data) : unserialize($data); 2727 if(!$data) $data = array(); 2728 } 2729 else $data = array(); 2730 2731 $this->setData($data); 2732 } 2733 return $this; 2734 } 2735 2736 /** 2737 * Apply current data to user data 2738 * @return e_user_pref 2739 */ 2740 public function apply() 2741 { 2742 $data = $this->hasData() ? $this->toString(true) : ''; 2743 $this->_user->set('user_prefs', $data); 2744 return $this; 2745 } 2746 2747 /** 2748 * Save and apply user preferences 2749 * @param boolean $from_post 2750 * @param boolean $force 2751 * @return boolean success 2752 */ 2753 public function save($from_post = false, $force = false, $session_messages = false) 2754 { 2755 if($this->_user->getId()) 2756 { 2757 if($from_post) 2758 { 2759 $this->mergePostedData(false, true, false); 2760 } 2761 if($force || $this->dataHasChanged()) 2762 { 2763 $data = $this->toString(true); 2764 $this->apply(); 2765 return (e107::getDb('user_prefs')->update('user', "user_prefs='{$data}' WHERE user_id=".$this->_user->getId()) ? true : false); 2766 } 2767 return 0; 2768 } 2769 return false; 2770 } 2771 2772 /** 2773 * Remove & apply user preferences, optionally - save to DB 2774 * @return boolean success 2775 */ 2776 public function delete($ids, $destroy = true, $session_messages = false) // replaced $save = false for PHP7 fix. 2777 { 2778 $this->removeData()->apply(); 2779 // if($save) return $this->save(); //FIXME adjust within the context of the variables in the method. 2780 return true; 2781 } 2782} 2783