[ Index ]

PHP Cross Reference of PivotX trunk SVN

title

Body

[close]

/pivotx/ -> objects.php (source)

   1  <?php
   2  
   3  // ---------------------------------------------------------------------------
   4  //
   5  // PIVOTX - LICENSE:
   6  //
   7  // This file is part of PivotX. PivotX and all its parts are licensed under
   8  // the GPL version 2. see: http://docs.pivotx.net/doku.php?id=help_about_gpl
   9  // for more information.
  10  //
  11  // $Id: objects.php 4165 2012-05-08 10:24:08Z hansfn $
  12  //
  13  // ---------------------------------------------------------------------------
  14  
  15  
  16  
  17  /**
  18   * Base Configuration Class
  19   *
  20   * Handle the loading and saving configuration data.
  21   * It contains defaults flows of reading, verifying/fixing and saving of configuration data.
  22   *
  23   * Description of the load call:
  24   * - __construct
  25   *   - loadConfig
  26   *     - verifyConfig
  27   *     - fixConfig
  28   *     - saveConfig
  29   *       - organizeConfig
  30   *   - organizeConfig
  31   *   - initConfig
  32   *
  33   * Save call:
  34   * - saveConfig
  35   *   - organizeConfig
  36   */
  37  class BaseConfig {
  38  
  39      var $configfile = '';
  40      var $data = array();
  41      var $changed = false;
  42      var $upgraded = false;
  43  
  44      /**
  45       * Constructor
  46       *
  47       * @param filename      configuration filename
  48       * @param db_path       path to pivotx db directory, when false we assume config has been loaded
  49       */
  50      function __construct($filename, $db_path = false) {
  51          if ($db_path === false) {
  52              global $PIVOTX;
  53  
  54              $db_path = $PIVOTX['paths']['db_path'];
  55          }
  56          
  57          $this->configfile = $db_path . $filename;
  58  
  59          $this->loadConfig();
  60  
  61          $this->organizeConfig();
  62  
  63          $this->initConfig();
  64      }
  65  
  66      /**
  67       * Set upgraded
  68       */
  69      protected function setUpgraded($upgraded=true) {
  70          $this->upgraded = $upgraded;
  71      }
  72  
  73      /**
  74       * Set changed flag
  75       */
  76      protected function setChanged($changed=true) {
  77          $this->changed = $changed;
  78      }
  79  
  80      /**
  81       * Load and verify config
  82       */
  83      protected function loadConfig() {
  84          $this->data = loadSerialize($this->configfile, true);
  85  
  86          if (!$this->verifyConfig()) {
  87              $this->fixConfig();
  88  
  89              $this->saveConfig(true);
  90          }
  91      }
  92  
  93      /**
  94       * Verify configuration (this should be overwritten in subclass)
  95       *
  96       * @return boolean    true if configuration is ok
  97       */
  98      protected function verifyConfig() {
  99          return true;
 100      }
 101  
 102      /**
 103       * Fix configuration (this should be overwritten in subclass)
 104       *
 105       * This is called when verifyConfig() fails.
 106       */
 107      protected function fixConfig() {
 108      }
 109  
 110      /**
 111       * Organize configuration
 112       *
 113       * @param boolean    true, if configuration can be saved
 114       *
 115       * This is called when just loaded and before configuration is saved.
 116       */
 117      protected function organizeConfig() {
 118          if (is_array($this->data)) {
 119              ksort($this->data);
 120          }
 121  
 122          return true;
 123      }
 124  
 125      /**
 126       * Initialise configuration after has been read, fixed and organized.
 127       */
 128      protected function initConfig() {
 129      }
 130  
 131  
 132      /**
 133       * Save configuration if 'safe' to do so
 134       */
 135      protected function saveConfig($force_changed=false) {
 136          if ($force_changed) {
 137              $this->setChanged();
 138          }
 139  
 140          if ($this->changed) {
 141              $writable = $this->organizeConfig();
 142              
 143              if ((defined('PIVOTX_INADMIN') || defined('PIVOTX_INAJAXHELPER')) && (is_array($this->data)) && ($writable)) {
 144                  saveSerialize($this->configfile, $this->data);
 145              }
 146          }
 147      }
 148  
 149      /**
 150       * Return configuration-array size
 151       */
 152      public function count() {
 153          if (!is_array($this->data)) {
 154              return 0;
 155          }
 156          return count($this->data);
 157      }
 158  
 159      /**
 160       * Print a comprehensible representation of the users
 161       *
 162       */
 163      function print_r() {
 164          echo "<pre>\n";
 165          print_r($this->data);
 166          echo "</pre>\n";
 167      }
 168  
 169      /**
 170       * Old save version
 171       *
 172       * This function force a save anyway.
 173       */
 174      public function save()
 175      {
 176          return $this->saveConfig(true);
 177      }
 178  }
 179  
 180  /**
 181   * Takes care of all configuration settings. The configuration is stored in
 182   * pivotx/db/ser_config.php, but is completely accessible through this object.
 183   * Saving is automagical and only when something has changed.
 184   *
 185   */
 186  class Config extends BaseConfig {
 187      public function __construct($sites_path = '') {
 188          $db_path = dirname(__FILE__) . '/' . $sites_path . 'db/';
 189  
 190          parent::__construct('ser_config.php',$db_path);
 191      }
 192  
 193      public function Config($sites_path = '') {
 194          $this->__construct($sites_path);
 195      }
 196  
 197      protected function verifyConfig() {
 198          if ($this->count() < 5) {
 199              return false;
 200          }
 201  
 202          $default = getDefaultConfig();
 203          foreach($default as $key=>$value) {
 204              if (!isset($this->data[$key])) {
 205                  return false;
 206              }
 207          }
 208  
 209          if (!isset($this->data['server_spam_key']) || empty($this->data['server_spam_key'])) {
 210              return false;
 211          }
 212  
 213          return true;
 214      }
 215  
 216      protected function fixConfig() {
 217          if ($this->count() < 5) {
 218              $this->readOld();
 219  
 220              $this->setChanged();
 221          }
 222  
 223          $default = getDefaultConfig();
 224          foreach($default as $key=>$value) {
 225  
 226              if (!isset($this->data[$key])) {
 227                  $this->data[$key] = $value;
 228  
 229                  $this->setChanged();
 230              }
 231          }
 232  
 233          // Seperate check for 'server_spam_key' since it is different for all PivotX install
 234          if (!isset($this->data['server_spam_key']) || empty($this->data['server_spam_key'])) {
 235              $server_spam_key = '';
 236              $possible_server_keys = array('SERVER_SIGNATURE','SERVER_ADDR','PHP_SELF','DOCUMENT_ROOT');
 237              foreach ($possible_server_keys as $key) {
 238                  if (isset($_SERVER[$key])) {
 239                      $server_spam_key .= $_SERVER[$key];
 240                  }
 241              }
 242              $server_spam_key .= time();
 243              $this->data['server_spam_key'] = md5($server_spam_key);
 244              
 245              $this->setChanged();
 246          }
 247  
 248          // If there's a file called 'pivotxdebugmode.txt', we'll enable debugging 
 249          if (file_exists(dirname(__FILE__)."/pivotxdebugmode.txt")) {
 250              $this->data['debug'] = 1;
 251          }
 252      }
 253  
 254      /**
 255       * If the config file is missing, we check if there's a pivot 1.x config
 256       * file that we can use. This function does some comversions to get it up
 257       * to date, and sets it in $this->data
 258       *
 259       */
 260      protected function readOld() {
 261          global $pivotx_path;
 262  
 263          // If the old config file doesn't exist or it isn't readable, we return false..
 264          if (!file_exists($pivotx_path.'pv_cfg_settings.php') || (!is_readable($pivotx_path.'pv_cfg_settings.php'))) {
 265              return false;
 266          }
 267  
 268          // Mark this configuration as updated from old PivotX
 269          $this->setUpgraded();
 270  
 271          // get the config file
 272          $fh = file($pivotx_path.'pv_cfg_settings.php');
 273  
 274          foreach ($fh as $fh_this) {
 275              @list($name, $val) = explode("!", $fh_this);
 276              $Cfg[trim($name)] = trim($val);
 277          }
 278          //GetUserInfo();
 279          //ExpandSessions();
 280  
 281          @$Cfg['ping_urls']=str_replace("|", "\n", $Cfg['ping_urls']);
 282          @$Cfg['default_introduction']=str_replace("|", "\n", $Cfg['default_introduction']);
 283  
 284          if (!isset($Cfg['selfreg'])) { $Cfg['selfreg']= 0; }
 285          if (!isset($Cfg['xmlrpc'])) { $Cfg['xmlrpc']= 0; }
 286          if (!isset($Cfg['hashcash'])) { $Cfg['hashcash']= 0; }
 287          if (!isset($Cfg['spamquiz'])) { $Cfg['spamquiz']= 0; }
 288          if (!isset($Cfg['hardened_trackback'])) { $Cfg['hardened_trackback']= 0; }
 289          if (!isset($Cfg['moderate_comments'])) { $Cfg['moderate_comments']= 0; }
 290          if (!isset($Cfg['lastcomm_amount_max'])) { $Cfg['lastcomm_amount_max'] = 60; }
 291  
 292          if (!isset($Cfg['tag_cache_timeout'])) { $Cfg['tag_cache_timeout'] = 60; }
 293          if (!isset($Cfg['tag_flickr_enabled'])) { $Cfg['tag_flickr_enabled'] = 1; }
 294          if (!isset($Cfg['tag_flickr_amount'])) { $Cfg['tag_flickr_amount'] = 6; }
 295          if (!isset($Cfg['tag_fetcher_enabled'])) { $Cfg['tag_fetcher_enabled'] = 1; }
 296          if (!isset($Cfg['tag_fetcher_amount'])) { $Cfg['tag_fetcher_amount'] = 10; }
 297          if (!isset($Cfg['tag_min_font'])) { $Cfg['tag_min_font'] = 9; }
 298          if (!isset($Cfg['tag_max_font'])) { $Cfg['tag_max_font'] = 42; }
 299  
 300          if(!isset($Cfg['server_spam_key']))  {
 301              $key = $_SERVER['SERVER_SIGNATURE'].$_SERVER['SERVER_ADDR'].$_SERVER['SCRIPT_URI'].$_SERVER['DOCUMENT_ROOT'].time();
 302              $Cfg['server_spam_key'] = md5($key);
 303          }
 304  
 305          // Remove stuff we don't need:
 306          unset($Cfg['session_length']);
 307          unset($Cfg['sessions']);
 308          unset($Cfg['users']);
 309          unset($Cfg['userfields']);
 310          unset($Cfg['<?php']);
 311          unset($Cfg['?>']);
 312  
 313  
 314          foreach ($Cfg as $key => $val) {
 315              if ( (strpos($key,'uf-')===0) || (strpos($key,'user-')===0) ) {
 316                  unset($Cfg[$key]);
 317              }
 318          }
 319  
 320          $this->data = $Cfg;
 321      }
 322  
 323      /**
 324       * Return the entire config as a big array.. It's probable better to use
 325       * $PIVOTX['config']->get() if you only need one or few items.
 326       *
 327       * @see $this->get
 328       * @return array
 329       */
 330      function getConfigArray() {
 331  
 332          return $this->data;
 333  
 334      }
 335  
 336      /**
 337       * Sets a configuration value, and then saves it.
 338       *
 339       * @param string $key
 340       * @param unknown_type $value
 341       */
 342      function set($key, $value) {
 343  
 344          // Empty checkboxes are passed by jQuery as string 'undefined', but we want to store them as integer '0'
 345          if ($value==="undefined") { $value=0; }
 346  
 347          // Offline configuration is not saved in the normal configuration file
 348          if (substr($key,0,8) == 'offline_') {
 349              PivotxOffline::setConfig(substr($key,8),$value);
 350              return;
 351          }
 352  
 353          // Only set (and save) if the value has actually changed.
 354          if (empty($this->data[safeString($key)]) || $value !== $this->data[safeString($key)] ) {
 355  
 356              $this->data[safeString($key)] = $value;
 357  
 358              $this->saveConfig(true);
 359          }
 360      }
 361  
 362      /**
 363       * Delete a configuration value. Use with extreme caution. Saves the
 364       * configuration afterwards
 365       *
 366       * @param string $key
 367       */
 368      function del($key) {
 369          // Old pre PivotX 2.0 configuration didn't use safe_string
 370          // on the key - we are handling it here.
 371          if (isset($this->data[safeString($key)])) {
 372              unset($this->data[safeString($key)]);
 373          } else {
 374              unset($this->data[$key]);
 375          }
 376  
 377          $this->saveConfig(true);
 378      }
 379  
 380      /**
 381       * Gets a single value from the configuration.
 382       *
 383       * @param string $key
 384       * @return string
 385       */
 386      function get($key) {
 387          if (isset($this->data[$key])) {
 388              return $this->data[$key];
 389          } else {
 390              return false;
 391          }
 392      }
 393  }
 394  
 395  /**
 396   * Since PHP4 doesn't allow class constants, we define the userlevels as 
 397   * global constants.
 398   */
 399  define("PIVOTX_UL_NOBODY", -1);
 400  define("PIVOTX_UL_MOBLOGGER", 0);
 401  define("PIVOTX_UL_NORMAL", 1);
 402  define("PIVOTX_UL_ADVANCED", 2);
 403  define("PIVOTX_UL_ADMIN", 3);
 404  define("PIVOTX_UL_SUPERADMIN", 4);
 405  
 406  /**
 407   * Portable PHP password hashing framework (phpass) for PivotX:
 408   *
 409   * The framework can be completely disabled by setting "disable_phpass" to 1
 410   * in the advanced configuration. This is not recommended. If it is disabled,
 411   * a salted md5 sum is used for password hashing.
 412   * 
 413   * 1) The standard log2 number of iterations for password stretching. This 
 414   * should be increased from time to time to counteract increases in the speed 
 415   * and power of computers available to crack the hashes. However, since the 
 416   * current hashing algorithms aren't capable of running in parallell in PHP, 
 417   * it shouldn't be increased too often. (It should never exceed 31.)
 418   */
 419  define('PIVOTX_PASSWORD_HASH_COUNT', 9);
 420  /**
 421   * 2) By default, portable hashes are used for maximum portability. Portable 
 422   * hashes can be disabled by setting "password_non_portable_hashes"
 423   * to 1 in the advanced configuration. Non-portable hashes are more secure, 
 424   * but can be a problem on shared hosting or if you need to move your site 
 425   * between different servers. 
 426   */
 427  define('PIVOTX_PASSWORD_PORTABLE_HASHES', true);
 428  
 429  /**
 430   * This Class handles all operations with regard to users: adding, deleting,
 431   * getting info, etc.
 432   *
 433   */
 434  class Users extends BaseConfig {
 435  
 436      public function __construct() {
 437          parent::__construct('ser_users.php');
 438      }
 439  
 440      public function Users() {
 441          $this->__construct();
 442      }
 443  
 444      protected function verifyConfig() {
 445          if ($this->count() < 1) {
 446              return false;
 447          }
 448  
 449          return true;
 450      }
 451  
 452      protected function fixConfig() {
 453          if (count($this->data) < 5) {
 454              $this->readOld();
 455  
 456              $this->setChanged();
 457          }
 458      }
 459  
 460      protected function organizeConfig() {
 461          // Make sure the users are sorted as intended.
 462          uasort($this->data, array($this, 'sort'));
 463  
 464          if ($this->count() < 1) {
 465              return false;
 466          }
 467  
 468          return true;
 469      }
 470  
 471      protected function readOld() {
 472          global $pivotx_path;
 473  
 474          // If the old config file doesn't exist or it isn't readable, we return false..
 475          if (!file_exists($pivotx_path.'pv_cfg_settings.php') || (!is_readable($pivotx_path.'pv_cfg_settings.php'))) {
 476              return false;
 477          }
 478  
 479          // Mark this configuration as updated from old PivotX
 480          $this->setUpgraded();
 481  
 482          // get the config file
 483          $fh = file($pivotx_path.'pv_cfg_settings.php');
 484  
 485          foreach ($fh as $fh_this) {
 486              @list($name, $val) = explode("!", $fh_this);
 487              $Cfg[trim($name)] = trim($val);
 488          }
 489  
 490          if(isset($Cfg['users']))  {
 491              foreach(explode('|', trim($Cfg['users'])) as $inc => $user){
 492                  $userdata = array();
 493                  $userdata['username'] = $user;
 494                  foreach(explode('|-|' , $Cfg['user-' . $user]) as $var => $val){
 495                      list($Nvar, $Nval) = explode('|', $val);
 496                      if ($Nvar == 'nick') {
 497                          $userdata['nickname'] = $Nval;
 498                      } elseif ($Nvar == 'pass') {
 499                          $userdata['md5_pass'] = $Nval;
 500                      } else {
 501                          $userdata[$Nvar] = $Nval;
 502                      }
 503                  }
 504                  list($userdata['language']) = explode("_",$userdata['language']);
 505                  $this->addUser($userdata);
 506              }
 507          }
 508      }
 509  
 510      /**
 511       * Add a user to Pivot
 512       *
 513       * @param array $user
 514       */
 515      function addUser($user) {
 516          global $PIVOTX;
 517  
 518          // Make sure the username is OK..
 519          $user['username'] = strtolower(safeString($user['username']));
 520  
 521          if ($this->getUser($user['username'])!==false) {
 522              // this username is already taken..
 523              return false;
 524          }
 525  
 526          $newuser = array(
 527              'username' => $user['username'],
 528              'email' => $user['email'],
 529              'userlevel' => $user['userlevel'],
 530              'nickname' => $user['nickname'],
 531              'language' => $user['language'],
 532              'author_user' => $user['author_user'],
 533              'image' => $user['image'],
 534              'text_processing' => $user['text_processing']
 535          );
 536  
 537          if (!isset($user['pass1']) && isset($user['md5_pass'])) {
 538              // User comes from old (1.x) config so we don't have the clear text password.
 539              $newuser['password'] = $user['md5_pass'];
 540              $newuser['salt'] = '';
 541          } else {
 542              $newuser = $this->hashPassword($newuser, $user['pass1']);
 543          }
 544  
 545          $this->data[] = $newuser;
 546  
 547          $this->saveConfig(true);
 548  
 549      }
 550  
 551      function deleteUser($username) {
 552  
 553          if ($this->count() > 1) {
 554              foreach($this->data as $key=>$user) {
 555                  if ($username == $user['username']) {
 556                      unset($this->data[$key]);
 557                  }
 558              }
 559          }
 560  
 561          $this->saveConfig(true);
 562  
 563      }
 564  
 565      /**
 566       * Update a given property of a user
 567       *
 568       * @param string $username
 569       * @param array $properties
 570       * @see $this->save
 571       */
 572      function updateUser($username, $properties) {
 573  
 574          // Select the correct user
 575          foreach ($this->data as $key=>$user) {
 576              if ($username == $user['username']) {
 577  
 578                  // Set the properties
 579                  foreach($properties as $property => $value) {
 580  
 581                      switch ($property) {
 582                          case "email":
 583                          case "nickname":
 584                          case "language":
 585                          case "text_processing":
 586                          case "lastseen":
 587                          case "userlevel":
 588                          case "image":
 589                          case "author_user":
 590                              $this->data[$key][$property] = $value;
 591                              break;
 592  
 593                          case "reset_id":
 594                              if ($value!="") {
 595                                  $this->data[$key][$property] = $value;
 596                              } else {
 597                                  unset($this->data[$key][$property]);
 598                              }
 599                              break;
 600  
 601                          case "pass1":
 602                              if ( ($value!="") && ($value!="******")) {
 603                                  $this->data[$key] = $this->hashPassword($user, $value);
 604                              }
 605  
 606                          default:
 607                              break;
 608                      }
 609  
 610                  }
 611  
 612              }
 613  
 614          }
 615  
 616          $this->saveConfig(true);
 617      }
 618  
 619      /**
 620       * Check if a given password matches the one stored.
 621       *
 622       * @param string $username
 623       * @param string $password
 624       * @return boolean
 625       */
 626      function checkPassword($username, $password) {
 627          global $PIVOTX;
 628  
 629          foreach($this->data as $user) {
 630  
 631              if ($username==$user['username']) {
 632                  if ($user['salt'] == 'phpass') {
 633                      require_once($PIVOTX['paths']['pivotx_path'] . 'includes/PasswordHash.php');
 634                      // We don't really need to set portability correctly when checking 
 635                      // the password (since the hashing method is stored in the hash), 
 636                      // but it's clearer to use the same code everywhere.
 637                      if ($PIVOTX['config']->get('password_non_portable_hashes')) {
 638                          $portable = false;
 639                      } else {
 640                          $portable = PIVOTX_PASSWORD_PORTABLE_HASHES;
 641                      }
 642                      $phpass = new PasswordHash(PIVOTX_PASSWORD_HASH_COUNT, $portable);
 643                      return $phpass->CheckPassword($password, $user['password']);
 644                  } else {
 645                      if (md5($password . $user['salt']) == $user['password']) {
 646                          return true;
 647                      }
 648                  }
 649                  break;
 650              }
 651  
 652          }
 653  
 654          return false;
 655  
 656      }
 657  
 658      /**
 659       * Hash a given password (for a given user).
 660       *
 661       * @param array $user
 662       * @param string $password
 663       * @return boolean
 664       */
 665      function hashPassword($user, $password) {
 666          global $PIVOTX;
 667  
 668          if (!$PIVOTX['config']->get('disable_phpass')) {
 669              require_once($PIVOTX['paths']['pivotx_path'] . 'includes/PasswordHash.php');
 670              if ($PIVOTX['config']->get('password_non_portable_hashes')) {
 671                  $portable = false;
 672              } else {
 673                  $portable = PIVOTX_PASSWORD_PORTABLE_HASHES;
 674              }
 675              $phpass = new PasswordHash(PIVOTX_PASSWORD_HASH_COUNT, $portable);
 676              $user['salt'] = 'phpass';
 677              $user['password'] =  $phpass->HashPassword($password);
 678          } else {
 679              $user['salt'] = md5(rand(1,999999) . mktime());  
 680              $user['password'] = md5( $password . $user['salt']);
 681          }
 682  
 683          return $user;
 684  
 685      }
 686  
 687      /**
 688       * Check if a given $username is a user.
 689       *
 690       * @param string $name
 691       * @return boolean
 692       */
 693      function isUser($username) {
 694  
 695          if ($this->getUser($username) === false) {
 696              return false;
 697          } else {
 698              return true;
 699          }
 700  
 701      }
 702  
 703      /**
 704       * Get the specifics for a given user by its username.
 705       *
 706       * @param string $username
 707       * @return array
 708       */
 709      function getUser($username) {
 710  
 711          foreach($this->data as $user) {
 712  
 713              if ( ($username==$user['username']) ) {
 714                  return $user;
 715              }
 716  
 717          }
 718  
 719          return false;
 720  
 721      }
 722  
 723      /**
 724       * Get the specifics for a given user by its nickname.
 725       *
 726       * @param string $username
 727       * @return array
 728       */
 729      function getUserByNickname($username) {
 730  
 731          foreach($this->data as $user) {
 732  
 733              if ( strtolower($username) == strtolower($user['nickname']) ) {
 734                  return $user;
 735              }
 736  
 737          }
 738  
 739          return false;
 740  
 741      }
 742  
 743      /**
 744       * Get a list of the Usernames
 745       *
 746       * @return array
 747       */
 748      function getUsernames() {
 749  
 750          $res = array();
 751  
 752          foreach($this->data as $user) {
 753              $res[]=$user['username'];
 754          }
 755  
 756          return $res;
 757  
 758      }
 759  
 760      /**
 761       * Get a list of the Users Nicknames
 762       *
 763       * @return array
 764       */
 765      function getUserNicknames() {
 766  
 767          $res = array();
 768  
 769          foreach($this->data as $user) {
 770              $res[ $user['username'] ] = $user['nickname'];
 771          }
 772  
 773          return $res;
 774  
 775      }
 776  
 777      /**
 778       * Get a list of the Users Email adresses
 779       *
 780       * @return array
 781       */
 782      function getUserEmail() {
 783  
 784          $res = array();
 785  
 786          foreach($this->data as $user) {
 787              $res[ $user['username'] ] = $user['email'];
 788          }
 789  
 790          return $res;
 791  
 792      }
 793  
 794      /**
 795       * Get all users as an array
 796       *
 797       * @return array
 798       */
 799      function getUsers() {
 800  
 801          return $this->data;
 802  
 803      }
 804      
 805      /**
 806       * Determines if $currentuser (or 'the current user', if left empty) is allowed
 807       * to edit a page or entry that's owned by $contentowner.
 808       *
 809       * @param string $contentowner
 810       * @param string $currentuser
 811       * @return boolean
 812       */
 813      function allowEdit($contenttype, $contentowner="", $currentuser="") {
 814          global $PIVOTX;
 815  
 816          // Default to the current logged in user.
 817          if (empty($currentuser)) {
 818              $currentuser = $PIVOTX['session']->currentUsername();
 819          }
 820  
 821          // Fetch the current user..
 822          $currentuser = $PIVOTX['users']->getUser( $currentuser );
 823          $currentuserlevel = (!$currentuser?PIVOTX_UL_NOBODY:$currentuser['userlevel']);
 824          
 825          // Always allow editing for superadmins - no matter content type.
 826          if ($currentuserlevel==PIVOTX_UL_SUPERADMIN) {
 827              return true;
 828          } 
 829  
 830          // Fetch the owner..
 831          $contentowner = $PIVOTX['users']->getUser( $contentowner );
 832          $contentownerlevel = (!$contentowner?PIVOTX_UL_NOBODY:$contentowner['userlevel']);
 833  
 834          // Now run the checks for different content types
 835          if ($contenttype == 'chapter') {
 836  
 837              // Only sdministrator and superadmins can add, edit and delete chapters.
 838              if ($currentuserlevel>=PIVOTX_UL_ADMIN) {
 839                  return true;
 840              } 
 841  
 842          } else if (($contenttype == 'entry') || ($contenttype == 'page')) {
 843  
 844              // Get the value (if any) of allow_edit_for_own_userlevel setting
 845              $allowsamelevel = getDefault( $PIVOTX['config']->get('allow_edit_for_own_userlevel'), PIVOTX_UL_SUPERADMIN);
 846  
 847              if ($contentowner['username']==$currentuser['username']) {
 848                  // Always allow editing of your own content..
 849                  return true;
 850              } else  if ($currentuserlevel > $contentownerlevel) {
 851                  // Allow editing content for items owned by lower levels.
 852                  return true;
 853              } else if ( ($currentuserlevel == $contentownerlevel) && ( $currentuserlevel >= $allowsamelevel) ) {
 854                  // Allow if userlevel is the same, and greater than or equal to $allowsamelevel
 855                  return true;
 856              }
 857  
 858          } else if (($contenttype == 'comment') || ($contenttype == 'trackback')) {
 859  
 860              if ($contentowner['username']==$currentuser['username']) {
 861                  // Always allow editing of comments/trackback on your own entries.
 862                  return true;
 863              } else  if ($currentuserlevel >= PIVOTX_UL_ADVANCED) {
 864                  return true;
 865              }
 866  
 867          } else {
 868              debug('Unknown content type');
 869          }
 870  
 871          // Disallow editing
 872          return false;
 873      }
 874      
 875      /**
 876       * Sort the users based on string comparison of username.
 877       *
 878       * @param array $a
 879       * @param array $b
 880       * @return int
 881       */
 882      function sort($a, $b) {
 883          global $PIVOTX;
 884  
 885          return strcmp($a['username'],$b['username']);
 886      }
 887  }
 888  
 889  /**
 890   * This class deals with the Weblogs.
 891   *
 892   */
 893  class Weblogs extends BaseConfig {
 894  
 895      var $default;
 896      var $current;
 897  
 898      public function __construct() {
 899          parent::__construct('ser_weblogs.php');
 900      }
 901  
 902      public function Weblogs() {
 903          $this->__construct();
 904      }
 905  
 906      public function verifyConfig() {
 907          if ($this->count() < 1) {
 908              return false;
 909          }
 910      }
 911  
 912      public function fixConfig() {
 913          if ($this->count() < 1) {
 914              $this->readOld();
 915  
 916              $this->setChanged();
 917          }
 918  
 919          if ($this->count() < 1) {
 920              // No weblogs, create one from scratch
 921              $this->add('weblog', __('My weblog'), 'pivotxdefault');
 922          }
 923      }
 924  
 925      protected function initConfig() {
 926          global $PIVOTX;
 927  
 928          foreach ($this->data as $key => $weblog) {
 929                     
 930              // Unset '$subkey' weblog -> compensates for an old bug
 931              if (!empty($this->data[$key]['sub_weblog']['$subkey'])) {
 932                  unset($this->data[$key]['sub_weblog']['$subkey']);
 933              }
 934              
 935              // Make sure all categories are arrays.
 936              if (is_array($weblog['sub_weblog'])) {
 937                  foreach ($weblog['sub_weblog'] as $subkey => $subweblog) {
 938                      if (!is_array($subweblog['categories'])) {
 939                          $this->data[$key]['sub_weblog'][$subkey]['categories'] = array($subweblog['categories']);
 940                      }
 941                  }
 942              }
 943   
 944              // Set the correct link to the weblog.
 945              if (empty($this->data[$key]['site_url'])) {
 946                  $this->data[$key]['site_url'] = "";
 947              }
 948              $this->data[$key]['link'] = $this->_getLink($key, $this->data[$key]['site_url']);
 949   
 950              // Set the 'categories' for the combined subweblogs..
 951              $this->data[$key]['categories'] = $this->getCategories($key);
 952  
 953          }
 954  
 955          // Make sure the weblogs are sorted as intended.
 956          $this->organizeConfig();
 957  
 958          // Set default weblog either as specified by the root in the config
 959          // or just by selecting the first in the weblo
 960          list($type, $root) = explode(":", $PIVOTX['config']->get('root'));
 961          if ($type=="w" && !empty($root) && isset($this->data[$root]) ) {
 962              $this->default = $root;
 963          } else {
 964              // Nothing to do but fall back to the first available weblog..
 965              reset($this->data);
 966              $this->default = key($this->data);
 967          }
 968      }
 969  
 970      protected function organizeConfig() {
 971          uasort($this->data, array($this, 'sort'));
 972  
 973          return true;
 974      }
 975  
 976      /**
 977       * Read old weblogs data..
 978       */
 979      function readOld() {
 980  
 981          if ((!file_exists(dirname(__FILE__)."/pv_cfg_weblogs.php")) || (!is_readable(dirname(__FILE__)."/pv_cfg_weblogs.php"))) {
 982              return false;
 983          }
 984  
 985          // Mark this configuration as updated from old PivotX
 986          $this->setUpgraded();
 987  
 988          $oldweblogs = loadSerialize(dirname(__FILE__)."/pv_cfg_weblogs.php", true);
 989  
 990          // Looping over old weblogs. For each old weblog, add a new one with
 991          // defaults values and then override the ones already set in the 
 992          // old config. This way we remove settings no longer present in 
 993          // PivotX. We also make sure the categories are all 'safe strings'..
 994          if(is_array($oldweblogs)) {
 995              foreach($oldweblogs as $weblogkey => $weblog) {
 996                  $newweblogkey = safeString($weblogkey,true);
 997                  $this->add($newweblogkey, $oldweblogs[$weblogkey]['name'], 'pivotxdefault');
 998                  foreach ($this->data[$newweblogkey] as $key => $value) {
 999                      if (isset($weblog[$key])) {
1000                          $this->data[$newweblogkey][$key] = $weblog[$key];
1001                      }
1002                  }
1003                  foreach($this->data[$newweblogkey]['sub_weblog'] as $subweblogkey => $subweblog) {
1004                      foreach($subweblog['categories'] as $categorykey => $category) {
1005                          $this->data[$newweblogkey]['sub_weblog'][$subweblogkey]['categories'][$categorykey] = 
1006                              safeString($category, true);
1007                      }
1008                  }
1009                  foreach($this->data[$newweblogkey]['categories'] as $categorykey => $category) {
1010                      $this->data[$newweblogkey]['categories'][$categorykey] = safeString($category, true);
1011                  }
1012              }
1013          }
1014  
1015      }
1016  
1017      /**
1018       * Sort the weblogs based on string comparison of name.
1019       *
1020       * @param array $a
1021       * @param array $b
1022       * @return int
1023       */
1024      function sort($a, $b) {
1025          global $PIVOTX;
1026  
1027          if ( (empty($a['sortorder']) && empty($b['sortorder'])) || ($a['sortorder'] == $b['sortorder']) ) {
1028              return strcmp($a['name'],$b['name']);
1029          } else {
1030              return ($a['sortorder'] < $b['sortorder']) ? -1 : 1;
1031          }
1032  
1033      }
1034  
1035      /**
1036       * Return all weblogs as an array
1037       *
1038       * @return array
1039       */
1040      function getWeblogs() {
1041  
1042          return $this->data;
1043  
1044      }
1045  
1046      /**
1047       * Returns an array with the weblog names.
1048       *
1049       * @return array
1050       */
1051      function getWeblogNames() {
1052  
1053          $names = array();
1054  
1055          foreach($this->data as $name=>$data) {
1056              $names[] = $name;
1057          }
1058  
1059          return $names;
1060  
1061      }
1062  
1063      /**
1064       * Check if a given $name is a weblog.
1065       *
1066       * @param string $name
1067       * @return boolean
1068       */
1069      function isWeblog($weblogname) {
1070  
1071          foreach ($this->data as $name=>$data) {
1072              if ($weblogname==$name) { return true; }
1073          }
1074  
1075          return false;
1076      }
1077  
1078  
1079      /**
1080       * Return the weblogs that have the given category or categories assigned
1081       * to them.
1082       *
1083       * @param array $categories
1084       */
1085      function getWeblogsWithCat($categories) {
1086  
1087          // $cats might be a string with one cat, if so, convert to array
1088          if (is_string($categories)) {
1089              $categories= array($categories);
1090          }
1091  
1092          $res=array();
1093  
1094          // search every weblog for all cats
1095          foreach ($this->data as $key => $weblog) {
1096  
1097              $weblogcategories = $this->getCategories($key);
1098  
1099              foreach ($categories as $cat) {
1100                  if (in_array($cat, $weblogcategories)) {
1101                      $res[]=$key;
1102                  }
1103              }
1104  
1105          }
1106  
1107          return array_unique($res);
1108      }
1109  
1110      /**
1111       * Get the categories from a certain weblog.
1112       *
1113       * @param string $weblogname
1114       * @return array
1115       */
1116      function getCategories($weblogname='') {
1117  
1118          // if no weblogname was given, use the 'current'..
1119          if (empty($weblogname)) { $weblogname = $this->getCurrent(); }
1120  
1121          $results = array();
1122  
1123          // Group the categories from the subweblogs together..
1124          if (is_array($this->data[$weblogname]['sub_weblog'])) {
1125              foreach ($this->data[$weblogname]['sub_weblog'] as $key=>$sub) {
1126  
1127                  $cats = $sub['categories'];
1128                  // $cats might be a string with one cat, if so, convert to array
1129                  if (is_string($cats)) {
1130                    $cats= array($cats);
1131                  }
1132  
1133                  // Add them to results
1134                  foreach($cats as $cat) {
1135                      $results[] = $cat;
1136                  }
1137              }
1138          }
1139  
1140          return array_unique($results);
1141  
1142      }
1143  
1144      /**
1145       * Returns the given weblog as an array. If no weblogname was given, use
1146       * the current weblog.
1147       *
1148       * @param string $weblogname
1149       * @return array
1150       */
1151      function getWeblog($weblogname='') {
1152  
1153          // if no weblogname was given, use the 'current'..
1154          if (empty($weblogname)) { $weblogname = $this->getCurrent(); }
1155  
1156          return $this->data[$weblogname];
1157  
1158      }
1159  
1160      /**
1161       * Return a subweblog as an array
1162       *
1163       * @param string $weblogname
1164       * @return array
1165       */
1166      function getSubweblog($weblogname='', $subweblogname) {
1167  
1168          // if no weblogname was given, use the 'current'..
1169          if (empty($weblogname)) { $weblogname = $this->getCurrent(); }
1170  
1171          return $this->data[$weblogname]['sub_weblog'][$subweblogname];
1172  
1173      }
1174  
1175      /**
1176       * Return the subweblogs of a given weblog as an array. It does this
1177       * by grabbing all [[weblog]] and [[ subweblog ]] tags from the templates
1178       * in the same folder as the template that was selected as the frontpage
1179       * template. Updates the subweblog info in the weblogs object.
1180       *
1181       * @param string $weblogname
1182       * @return array
1183       */
1184      function getSubweblogs($weblogname='') {
1185          global $PIVOTX;
1186  
1187          // if no weblogname was given, use the 'current'..
1188          if (empty($weblogname)) {
1189              $weblogname = $this->getCurrent();
1190          }
1191  
1192          $results = array();
1193  
1194          $weblog = $this->getWeblog($weblogname);
1195          $dirname = dirname($weblog['front_template']);
1196  
1197          if ( !is_dir($PIVOTX['paths']['templates_path'] . $dirname) || !is_readable($PIVOTX['paths']['templates_path'] . $dirname) ) {
1198              debug("Template folder $dirname doesn't exist or isn't readable");
1199              return array();
1200          }
1201  
1202          $dir = dir($PIVOTX['paths']['templates_path'] . $dirname);
1203  
1204          // Iterate through the files in the folder..
1205          while (false !== ($filename = $dir->read())) {
1206              $ext = getExtension($filename);
1207              if (in_array($ext, array('html', 'htm', 'tpl'))) {
1208  
1209                  $template_html = loadTemplate($dirname . "/" . $filename);
1210  
1211                  preg_match_all("/\[\[\s?(sub)?weblog([: ])(.*)?\]\]/mUi", $template_html, $matches);
1212  
1213                  foreach($matches[3] as $key=>$match) {
1214  
1215                      // if $matches[2][$key] was a ':', we know it's an old pivot 1.x style [[ subweblog:name ]]
1216                      // We also must handle optional arguments to the subweblog.
1217                      if ($matches[2][$key]==":") {
1218                          $name = explode(':',$match);
1219                          $results[] = trim($name[0]);
1220                      } else {
1221                          preg_match("/name=['\"]([^'\"]*)/mi", $match, $name);
1222                          // subweblog 'archive' has a special role so skip it here (not when in the dashboard)
1223                          // this is to disregard its number of entries e.g. for the pager display and initial building of the front page
1224                          // as no pager is allowed for archive display the number is only irrelevant.
1225                          if ($name[1]=='archive' && isset($PIVOTX['parser'])) { 
1226                              $name[1] = '';
1227                          }
1228                          if ($name[1]!="") {
1229                              $results[] = $name[1];
1230                          }
1231  
1232                      }
1233  
1234                  }
1235  
1236              }
1237          }
1238          $dir->close();
1239  
1240          $results = array_unique($results);
1241  
1242          // Remove any subweblogs that no longer exists from the weblog data.
1243          $updated = false;
1244          foreach ($this->data[$weblogname]['sub_weblog'] as $name => $value) {
1245              if (!in_array($name,$results)) {
1246                  unset($this->data[$weblogname]['sub_weblog'][$name]);
1247                  $updated = true;
1248              }
1249          }
1250          if ($updated) {
1251              $this->saveConfig(true);
1252          }
1253  
1254          return $results;
1255  
1256      }
1257  
1258      /**
1259       * Sets a given weblog as 'current' and returns false if the weblog
1260       * doesn't exist.
1261       *
1262       * @param string $weblogname
1263       * @return boolean
1264       */
1265      function setCurrent($weblogname='') {
1266          global $PIVOTX;
1267          
1268          $exists = true;
1269  
1270          if ( !isset($this->data[$weblogname]) ) {
1271              $exists = false;
1272              $weblogname = '';
1273          }
1274  
1275          if (empty($weblogname)) {
1276              $this->current = $this->default;
1277          } else  {
1278              $this->current = $weblogname;
1279          }
1280  
1281          return $exists;
1282  
1283      }
1284  
1285      /**
1286       * Sets a given weblog as 'current' based on a given category and returns false
1287       * if no matching weblog could be set.
1288       *
1289       * @param mixed $categories
1290       * @return boolean
1291       */
1292      function setCurrentFromCategory($categories) {
1293  
1294          // $categories might be a string (with comma seperated categories).
1295          if (is_string($categories)) {
1296              $categories = explode(",", $categories);
1297              $categories = array_map('trim', $categories);
1298          }
1299                  
1300          // Check categories in current weblog first (if set) and then the 
1301          // default weblog
1302          if (!empty($this->current)) {
1303              $weblogcategories = $this->data[$this->current]['categories'];
1304              foreach ($categories as $cat) {
1305                  if (in_array($cat, $weblogcategories)) {
1306                      return true;
1307                  }
1308              }
1309          } else {
1310              $weblogcategories = $this->data[$this->default]['categories'];
1311              foreach ($categories as $cat) {
1312                  if (in_array($cat, $weblogcategories)) {
1313                      $this->setCurrent($this->default);
1314                      return true;
1315                  }
1316              }
1317          }
1318  
1319          $skip_weblogs = array($this->current, $this->default);
1320  
1321          // search every weblog for all cats
1322          foreach ($this->data as $key => $weblog) {
1323  
1324              // Skip current and default since we checked them above
1325              if (in_array($key, $skip_weblogs)) {
1326                  continue;
1327              }
1328  
1329              $weblogcategories = $this->getCategories($key);
1330  
1331              foreach ($categories as $cat) {
1332                  if (in_array($cat, $weblogcategories)) {
1333                      $this->setCurrent($key);
1334                      return true;
1335                  }
1336              }
1337          }
1338  
1339          return false;
1340          
1341      }
1342  
1343      /**
1344       * Gets the currently active weblog.
1345       *
1346       * @return
1347       */
1348      function getCurrent() {
1349  
1350          // Set the current weblog, just to be sure.
1351          if (empty($this->current)) { $this->setCurrent(""); }
1352  
1353          return $this->current;
1354  
1355      }
1356  
1357      /**
1358       * Gets the default weblog.
1359       *
1360       * @return
1361       */
1362      function getDefault() {
1363  
1364          return $this->default;
1365  
1366      }
1367  
1368      /**
1369       * Add a new weblog, based on $theme. returns the internal name used for
1370       * the weblog.
1371       *
1372       * @param string $internal
1373       * @param string $name
1374       * @param string $theme
1375       * @return string
1376       */
1377      function add($internal, $name, $theme) {
1378  
1379          if ( ($internal=="") || isset($this->data[$internal])) {
1380              // Make a new 'name'..
1381              for($i=1;$i<1000;$i++) {
1382                  if (!isset($this->data[$internal . "_" . $i])) {
1383                      $internal = $internal . "_" . $i;
1384                      break;
1385                  }
1386              }
1387          }
1388  
1389          if ($theme=="blank") {
1390  
1391              $this->data[$internal]['name']=$name;
1392  
1393              $this->saveConfig(true);
1394  
1395          } else if ($theme=="pivotxdefault") {
1396  
1397              $weblog = getDefaultWeblog();
1398  
1399              $weblog['name'] = $name;
1400  
1401              if (empty($weblog['sortorder'])) { $weblog['sortorder'] = 10; }
1402              $this->data[$internal] = $weblog;
1403  
1404              $this->saveConfig(true);
1405  
1406  
1407          } else {
1408  
1409              $weblog = loadSerialize($theme, true);
1410  
1411              $weblog['name'] = $name;
1412  
1413              if (empty($weblog['sortorder'])) { $weblog['sortorder'] = 10; }
1414  
1415              $this->data[$internal] = $weblog;
1416  
1417              $this->saveConfig(true);
1418  
1419          }
1420  
1421          return $internal;
1422  
1423      }
1424  
1425      /**
1426       * Delete a weblog
1427       *
1428       * @param string $weblogname
1429       */
1430      function delete($weblogname) {
1431  
1432          unset($this->data[$weblogname]);
1433  
1434          $this->saveConfig(true);
1435  
1436      }
1437  
1438      /**
1439       * Export a weblog as a theme file. The file is saved in the same folder as
1440       * the weblog's frontpage template.
1441       *
1442       * @param string $weblogname
1443       */
1444      function export($weblogname) {
1445  
1446          $weblog = $this->data[$weblogname];
1447          $filename = dirname("./templates/".$weblog['front_template'])."/".$weblogname.".theme";
1448  
1449          saveSerialize($filename, $weblog);
1450  
1451      }
1452  
1453      /**
1454       * Sets a property of a given weblog
1455       *
1456       * @param string $weblogname
1457       * @param string $key
1458       * @param string $value
1459       */
1460      function set($weblogname, $key, $value) {
1461  
1462          if (isset($this->data[$weblogname])) {
1463  
1464              if (strpos($key, "#")>0) {
1465                  // we're setting something in a subweblog
1466                  // we get these as linkdump#categories = linkdump,books,movies
1467                  list($sub, $key) = explode("#", str_replace("[]", "", $key));
1468  
1469  
1470                  if (strpos($value, ",")>0) {
1471                      $value = explode(",", $value);
1472                  }
1473  
1474                  $this->data[$weblogname]['sub_weblog'][$sub][$key] = $value;
1475                  
1476                  // we must update the list of categories for the weblog
1477                  $categories = array();
1478                  foreach ($this->data[$weblogname]['sub_weblog'] as $subweblog) {
1479                      $categories = array_merge($categories,$subweblog['categories']);
1480                  } 
1481                  $this->data[$weblogname]['categories'] = array_unique($categories);
1482  
1483              } else {
1484  
1485                  if ($key == 'site_url') {
1486                      $this->data[$weblogname]['link'] = $this->_getLink($weblogname, $value);
1487                  }
1488  
1489                  $this->data[$weblogname][$key] = $value;
1490  
1491              }
1492  
1493              $this->saveConfig(true);
1494  
1495          } else {
1496  
1497              debug('tried to set a setting without passing a weblogname, or non-existing weblog');
1498  
1499          }
1500  
1501      }
1502  
1503      /**
1504       * Gets a property of a given weblog
1505       *
1506       * @param string $weblogname
1507       * @param string $key
1508       */
1509      function get($weblogname, $key) {
1510  
1511          if ($weblogname=="") {
1512              $weblogname = $this->getCurrent();
1513          }
1514  
1515          if (empty($this->data[$weblogname])) {
1516              static $noted = array();
1517              if (!isset($noted[$weblogname])) {
1518                  // only show this message once in the lifetime of the request
1519                  debug("Weblog '$weblogname' doesn't exist!");
1520  
1521                  $noted[$weblogname] = true;
1522              }
1523              $weblogname = key($this->data);
1524          }
1525  
1526          return $this->data[$weblogname][$key];
1527  
1528      }
1529  
1530      /**
1531       * Calculates the link for a given weblog
1532       *
1533       * @param string $value
1534       * @param string $weblogname
1535       */
1536      function _getLink($weblogname, $value) {
1537          global $PIVOTX;
1538          
1539          $link = trim($value);
1540          if ($link == '') {
1541              if ($PIVOTX['config']->get('mod_rewrite')==0) {
1542                  $link = $PIVOTX['paths']['site_url'] . '?w=' . $weblogname;
1543              } else {
1544                  $prefix = getDefault( $PIVOTX['config']->get('localised_weblog_prefix'), "weblog");
1545                  $link = $PIVOTX['paths']['site_url'] . $prefix . "/" . $weblogname;
1546              }
1547          } else {
1548              $ext = getExtension(basename($link));
1549              if ($ext == '') {
1550                  $link = addTrailingSlash($link);
1551              }
1552          }
1553  
1554          return $link;
1555      }
1556  
1557      function deleteCategoryFromWeblogs($name) {
1558          
1559          // Remove it from all weblogs as well.
1560          $weblogs = $this->data;
1561  
1562          foreach($weblogs as $weblogkey=>$weblog) {
1563              foreach($weblog['sub_weblog'] as $subweblogkey=>$subweblog) {
1564                  foreach($subweblog['categories'] as $catkey => $cat) {
1565                      if ($cat==$name) {
1566                          unset($weblogs[$weblogkey]['sub_weblog'][$subweblogkey]['categories'][$catkey]);
1567                      }
1568                  }
1569                  
1570              }
1571              foreach($weblogs[$weblogkey]['categories'] as $catkey => $cat) {
1572                  if ($cat==$name) {
1573                      unset($weblogs[$weblogkey]['categories'][$catkey]);
1574                  }
1575              }
1576          }
1577          
1578          $this->data = $weblogs;
1579  
1580          $this->saveConfig(true);
1581      }
1582  }
1583  
1584  /**
1585   * This class deals with the categories
1586   *
1587   */
1588  class Categories extends BaseConfig {
1589      public function __construct() {
1590          parent::__construct('ser_categories.php');
1591      }
1592  
1593      public function Categories() {
1594          $this->__construct();
1595      }
1596  
1597      protected function verifyConfig() {
1598          if ($this->count() < 1) {
1599              return false;
1600          }
1601  
1602          return true;
1603      }
1604  
1605      protected function fixConfig() {
1606          $save = false;
1607  
1608          if ($this->count() < 1) {
1609              // hmm, couldn't find the data.. Perhaps try to import it from old Pivot 1.x
1610              $this->readOld();
1611              $save = true;
1612          }
1613  
1614          if ($this->count()<1) {
1615              // if there still are no categories, load the defaults
1616              $this->data = getDefaultCategories();
1617              $save = true;
1618          }
1619  
1620          if ($save) {
1621              $this->saveConfig(true);
1622          }
1623      }
1624  
1625      protected function organizeConfig() {
1626          usort($this->data, array($this, 'sort'));
1627  
1628          return true;
1629      }
1630  
1631      /**
1632       * Sort the categories based on the order and string comparison
1633       * of display name if order is identical.
1634       *
1635       * @param array $a
1636       * @param array $b
1637       * @return int
1638       */
1639      function sort($a, $b) {
1640          global $PIVOTX;
1641  
1642          if ($PIVOTX['config']->get('sort_categories_by_alphabet')==true) {
1643              // If we set 'sort_categories_by_alphabet' to true, always sort by alphabet..
1644              return strcmp($a['display'],$b['display']);
1645          } else if ($a['order'] == $b['order']) {
1646              // Else sort by alphabet, if order is the same..
1647              return strcmp($a['display'],$b['display']);
1648          } else {
1649              // else sort by order..
1650              return ($a['order'] < $b['order']) ? -1 : 1;
1651          }
1652  
1653      }
1654  
1655      /**
1656       * Old save function
1657       */
1658      public function saveCategories() {
1659          return $this->saveConfig(true);
1660      }
1661  
1662      protected function readOld() {
1663          global $pivotx_path;
1664  
1665          // If the old config file doesn't exist or it isn't readable, we return false..
1666          if (!file_exists($pivotx_path.'pv_cfg_settings.php') || (!is_readable($pivotx_path.'pv_cfg_settings.php'))) {
1667              return false;
1668          }
1669  
1670          // Mark this configuration as updated from old PivotX
1671          $this->setUpgraded();
1672  
1673          // get the config file
1674          $fh = file($pivotx_path.'pv_cfg_settings.php');
1675  
1676          foreach ($fh as $fh_this) {
1677              @list($name, $val) = explode("!", $fh_this);
1678              $Cfg[trim($name)] = trim($val);
1679          }
1680          //GetUserInfo();
1681          //ExpandSessions();
1682  
1683          $catnames = explode("|",$Cfg['cats']);
1684  
1685          // Check which categories are "hidden"..
1686          if (isset($Cfg['cats-searchexclusion'])) {
1687              $hiddenarray = explode('|', strtolower($Cfg['cats-searchexclusion']));
1688          } else {
1689              $hiddenarray = array();
1690          }
1691  
1692          // Check the category order..
1693          if (isset($Cfg['cats-order'])) {
1694              $temp = explode('|-|', strtolower($Cfg['cats-order']));
1695  
1696              foreach($temp as $item) {
1697                  list($catname, $order) = explode("|", $item);
1698                  $orderarray[strtolower($catname)] = $order;
1699              }
1700          } else {
1701              $orderarray = array();
1702          }
1703  
1704  
1705          $cats = array();
1706  
1707          foreach ($catnames as $cat) {
1708  
1709              // Skip empty category names.
1710              $catname = trim($cat);
1711              if ($catname == '') {
1712                  continue;
1713              }
1714              
1715              $catname = strtolower($catname);
1716  
1717              if (isset($Cfg['cat-'.$cat])) {
1718                  $users = explode('|', strtolower($Cfg['cat-'.$cat]));
1719              } else {
1720                  $users = array();
1721              }
1722  
1723              // Make sure the users are 'safe strings'
1724              foreach($users as $key=>$user) {
1725                  $users[$key] = safeString($user, true);
1726              }
1727  
1728              $cats[] = array (
1729                  'name' => safeString($catname, true),
1730                  'display' => $cat,
1731                  'users' => $users,
1732                  'hidden' => (in_array($catname, $hiddenarray)) ? 1 : 0,
1733                  'order' => (isset($orderarray[$catname])) ? $orderarray[$catname] : 110,
1734                  );
1735  
1736          }
1737  
1738  
1739          $this->data = $cats;
1740  
1741      }
1742  
1743      /**
1744       * change the settings for an existing category, or modify an existing one.
1745       *
1746       * @param string $name
1747       * @param array $cat
1748       */
1749      function setCategory($name, $cat) {
1750  
1751          $name = strtolower(safeString($name));
1752          $cat['name'] = strtolower(safeString($cat['name']));
1753  
1754          foreach($this->data as $key=>$val) {
1755  
1756              if ($name==$val['name']) {
1757  
1758                  $this->data[$key] = $cat;
1759                  $this->saveConfig(true);
1760                  return;
1761              }
1762  
1763          }
1764  
1765          // Otherwise it must be a new one, let's add it:
1766          if(!empty($cat['name'])){
1767              $this->data[] = $cat;
1768              $this->saveConfig(true);
1769          }
1770  
1771  
1772      }
1773  
1774      /**
1775       * Get an array with all the categories. We filter the users to make sure we only
1776       * return users that still exist
1777       *
1778       * @return array
1779       */
1780      function getCategories() {
1781          global $PIVOTX;
1782  
1783          $results = $this->data;
1784          
1785          $users = $PIVOTX['users']->getUsernames();
1786          
1787          // Filter only existing users..
1788          foreach ($results as $key=>$value) {
1789              // Categories doesn't have to be assigned to any users.
1790              if (isset($results[$key]['users']) && is_array($results[$key]['users'])) {
1791                  $results[$key]['users'] = array_intersect($results[$key]['users'], $users);
1792              } else {
1793                  $results[$key]['users'] = array();
1794              }
1795  
1796          }
1797  
1798          return $results;
1799  
1800      }
1801      
1802  
1803  
1804      
1805  
1806      /**
1807       * Get a list of categories the user is allowed to post into
1808       */
1809      function allowedCategories($username) {
1810  
1811          $allowed = array();
1812  
1813          foreach($this->data as $cat) {
1814  
1815              if (in_array($username, $cat['users'])) {
1816                  $allowed[$cat['name']] = $cat['name'];
1817              }
1818  
1819          }
1820  
1821          return $allowed;
1822  
1823      }
1824  
1825      /**
1826       * Allow a user to post in this category
1827       *
1828       * @param string $catname
1829       * @param string $username
1830       */
1831      function allowUser($catname, $username) {
1832  
1833          // Loop through all available categories
1834          foreach($this->data as $key=>$cat) {
1835  
1836              if ($cat['name']==$catname) {
1837  
1838                  // Add the username
1839                  $this->data[$key]['users'][] = $username;
1840  
1841                  // But remove duplicates
1842                  $this->data[$key]['users'] = array_unique($this->data[$key]['users']);
1843  
1844              }
1845  
1846          }
1847  
1848      }
1849  
1850  
1851      /**
1852       * Disallow a user to post in this category
1853       *
1854       * @param string $catname
1855       * @param string $username
1856       */
1857      function disallowUser($catname, $username) {
1858  
1859          // Loop through all available categories
1860          foreach($this->data as $key=>$cat) {
1861  
1862              if ($cat['name']==$catname) {
1863  
1864                  // Loop through the users, and remove $username if present.
1865                  foreach($cat['users'] as $userkey=>$catuser){
1866                      if ($catuser==$username) {
1867                          unset($this->data[$key]['users'][$userkey]);
1868                      }
1869                  }
1870  
1871              }
1872  
1873          }
1874  
1875      }
1876  
1877  
1878      /**
1879       * Get a single category
1880       *
1881       * @param string $name
1882       * @return array
1883       */
1884      function getCategory($name) {
1885  
1886          foreach($this->data as $key=>$cat) {
1887  
1888              if ($cat['name']==$name) {
1889                  return $cat;
1890              }
1891  
1892          }
1893  
1894          return array();
1895  
1896      }
1897  
1898      /**
1899       * Get a list of all category names
1900       *
1901       * @return array
1902       */
1903      function getCategorynames() {
1904  
1905          $names = array();
1906  
1907          foreach($this->data as $cat) {
1908              $names[]=$cat['name'];
1909          }
1910          return $names;
1911  
1912      }
1913  
1914  
1915      /**
1916       * Check if a given $name is a category.
1917       *
1918       * @param string $name
1919       * @return boolean
1920       */
1921      function isCategory($name) {
1922  
1923          foreach($this->data as $cat) {
1924              if($name==$cat['name']) { return true; }
1925          }
1926  
1927          return false;
1928  
1929      }
1930  
1931  
1932  
1933      /**
1934       * Get a list of all category names in which we should NOT search
1935       *
1936       * @return array
1937       */
1938      function getSearchCategorynames() {
1939  
1940          $names = array();
1941  
1942          foreach($this->data as $cat) {
1943              if ($cat['hidden']!=1) {
1944                  $names[]=$cat['name'];
1945              }
1946          }
1947  
1948          return $names;
1949  
1950  
1951  
1952      }
1953  
1954  
1955      /**
1956       * Delete a single category
1957       *
1958       * @param string $name
1959       */
1960      function deleteCategory($name) {
1961          global $PIVOTX;
1962  
1963          foreach($this->data as $key=>$cat) {
1964  
1965              if ($cat['name']==$name) {
1966                  unset($this->data[$key]);
1967                  $this->saveConfig(true);
1968                  break;
1969              }
1970  
1971          }
1972  
1973          $PIVOTX['weblogs']->deleteCategoryFromWeblogs($name);
1974  
1975      }
1976  
1977  }
1978  
1979  /**
1980   * This class deals with Sessions: logging in, logging out, saving sessions
1981   * and performing checks for required userlevels.
1982   * 
1983   * This class protects the cookie/session against standard XSS attacks and 
1984   * sidejacking.
1985   *
1986   */
1987  class Session {
1988  
1989      var $permsessions, $logins, $maxlogins, $message;
1990      /**
1991       * Initialisation
1992       *
1993       * @return Session
1994       */
1995      function Session() {
1996          global $PIVOTX;
1997  
1998          $this->cookie_lifespan = 60*60*24*30;  // 30 days
1999          $this->cookie_name = "pivotxsession"; 
2000  
2001          $this->maxlogins = getDefault($PIVOTX['config']->get('loginlog_length'), 200);
2002          if (intval($this->maxlogins) < 10) {
2003              $this->maxlogins = 200;
2004          }
2005  
2006          // Select the secure bit for the session cookie. Setting it to true if
2007          // using HTTPS which stops sidejacking / session hijacking.
2008          if (isSecureConn()) {
2009              $this->cookie_secure = true;
2010          } else {
2011              $this->cookie_secure = false;
2012          }
2013          
2014          // Force cookie to be "HTTP only" to make cookie stealing harder - stops
2015          // standard XSS attacks. (Introduced in PHP 5.2.0.)
2016          if (checkVersion(phpversion(), '5.2.0')) {
2017              $this->cookie_httponly = true;
2018          } else {
2019              $this->cookie_httponly = false;
2020          }
2021  
2022          // On second thought, our CSRF check (that uses the double cookie submit 
2023          // test) needs to access the cookie ... We just can't use "HTTP only".
2024          $this->cookie_httponly = false;
2025  
2026          // Set to 'site url' instead of 'pivotx_url', because then we
2027          // can use 'edit this entry' and the like.
2028          $this->cookie_path = $PIVOTX['paths']['site_url'];
2029  
2030          // Don't set the domain for a cookie on a "TLD" - like localhost ...
2031          if (strpos($_SERVER["SERVER_NAME"], ".") > 0) {
2032              if (preg_match("/^www./",$_SERVER["SERVER_NAME"])) {
2033                  $this->cookie_domain = "." . preg_replace("/^www./", "", $_SERVER["SERVER_NAME"]);
2034              } else {
2035                  $this->cookie_domain = $_SERVER["SERVER_NAME"];
2036              }
2037          } else {
2038              $this->cookie_domain = "";
2039          }
2040  
2041          // Only set "HTTP only" if supported
2042          if ($this->cookie_httponly) {
2043              session_set_cookie_params($this->cookie_lifespan, 
2044                  $this->cookie_path, $this->cookie_domain, $this->cookie_secure, $this->cookie_httponly); 
2045          } else {
2046              session_set_cookie_params($this->cookie_lifespan, 
2047                  $this->cookie_path, $this->cookie_domain, $this->cookie_secure); 
2048          }
2049  
2050          session_start();
2051  
2052      }
2053  
2054      /**
2055       * Sets a cookie taking into account the path, domain, secure connection 
2056       * and if "HTTP only" is supported. Basically a wrapper around setcookie.
2057       *
2058       * @param string $name
2059       * @param string $value
2060       * @param string $time
2061       */
2062      function setCookie($name, $value, $time='') {
2063          if ($time == '') {
2064              $time = time() + $this->cookie_lifespan;
2065          }
2066          if ($this->cookie_httponly) {
2067              $res = setcookie($name, $value, $time, $this->cookie_path, 
2068                  $this->cookie_domain, $this->cookie_secure, $this->cookie_httponly );
2069          } else {
2070              $res = setcookie($name, $value, $time, $this->cookie_path, 
2071                  $this->cookie_domain, $this->cookie_secure );
2072          }
2073          
2074          // Add some debug output, if we couldn't set the cookie.
2075          if ($res==false) {
2076              debug("Couldn't set cookies! (probably because output has already started)");
2077              if (headers_sent($filename, $linenum)) {
2078                  debug("Headers already sent in $filename on line $linenum");
2079              } else {
2080                  debug("Headers have not been sent yet. Something's wonky.");
2081              }
2082          }
2083          
2084      }
2085  
2086      /**
2087       * Verify if whomever requested the current page is logged in as a user,
2088       * or else attempt to (transparently) continue from a saved session.
2089       *
2090       * @return boolean
2091       */
2092      function isLoggedIn() {
2093          global $PIVOTX;
2094          
2095          $this->loadPermsessions();
2096  
2097          $sessioncookie = (!empty($_COOKIE['pivotxsession'])) ? $_COOKIE['pivotxsession'] : $_POST['pivotxsession'];
2098  
2099          if (isset($_SESSION['user']) && isset($_SESSION['user']['username']) && ($_SESSION['user']['username']!="") ) {
2100  
2101              // User is logged in!
2102              
2103              // Check if we're in the saved sessions.. 
2104              if (!empty($sessioncookie) && !isset($this->permsessions[$sessioncookie])) {
2105              
2106                  $this->permsessions[ $sessioncookie ] = array(
2107                      'username' => $_SESSION['user']['username'],
2108                      'ip' => $_SERVER['REMOTE_ADDR'],
2109                      'lastseen' => time()
2110                  );
2111                  $this->savePermsessions();         
2112              }
2113              
2114              return true;
2115  
2116          } else {
2117  
2118              // See if we can continue a stored session..
2119  
2120              // Check if we have a pivotxsession cookie that matches a saved session..
2121              if ( (!empty($sessioncookie)) && (isset($this->permsessions[$sessioncookie])) ) {
2122  
2123                  $savedsess = $this->permsessions[ $sessioncookie ];
2124                  
2125                  // Check if the IP in the saved session matches the IP of the user..
2126                  if ($_SERVER['REMOTE_ADDR'] == $savedsess['ip']) {
2127  
2128                      // Check if the 'lastseen' wasn't too long ago..
2129                      if (time() < ($savedsess['lastseen'] + $this->cookie_lifespan) ) {
2130  
2131                          // Finally check if the user in the stored session still exists.
2132                          if (!$PIVOTX['users']->isUser($savedsess['username'])) {
2133                              return false;
2134                          }
2135  
2136                          // If we get here, we can restore the session!
2137  
2138                          $_SESSION['user']= $PIVOTX['users']->getUser($savedsess['username']);
2139  
2140                          // Update the 'lastseen' in permanent sessions.
2141                          $this->permsessions[ $sessioncookie ]['lastseen'] = time();
2142                          $this->savePermsessions();
2143  
2144                          // Add the 'lastseen' to the user settings.
2145                          $PIVOTX['users']->updateUser($savedsess['username'], array('lastseen'=>time()) );
2146                          $_SESSION['user']['lastseen'] = time();
2147  
2148                          // Set the session cookie as session variable.
2149                          $_SESSION['pivotxsession'] = $sessioncookie;
2150  
2151                          return true;
2152  
2153                      }
2154  
2155                  }
2156  
2157              }
2158              return false;
2159  
2160          }
2161  
2162      }
2163  
2164      /**
2165       * Attempt to log in a user, using the passed credentials. If succesfull,
2166       * the session info is updated and 'true' is returned. When unsuccesful
2167       * the session remains unaltered, and false is returned
2168       *
2169       *
2170       * @param string $username
2171       * @param string $password
2172       * @param int $stay
2173       * @return boolean
2174       */
2175      function login($username, $password, $stay) {
2176          global $PIVOTX;
2177  
2178          $this->loadLogins();
2179  
2180          if (!$this->checkFailedLogins()) {
2181              debug(sprintf(__("Blocked login attempt from '%s'."), $_SERVER['REMOTE_ADDR']));
2182              $this->message = __('Too many failed login attempts from this IP address. ' . 
2183                  'Please contact your site administrator to unblock your account.');
2184              return false;
2185          }
2186  
2187          $username = strtolower($username);
2188  
2189          $match = $PIVOTX['users']->checkPassword($username, $password);
2190  
2191          if (!$match) {
2192  
2193              $this->message = __('Incorrect username/password');
2194              $this->logFailedLogin();
2195              return false;
2196  
2197          } else {
2198  
2199              $this->message = __('Successfully logged in');
2200              $key = makeKey(16);
2201              $_SESSION['pivotxsession'] = $key;
2202  
2203              // Add the 'lastseen' to the user settings and remove and reset_ids.
2204              $PIVOTX['users']->updateUser($username, array('lastseen'=>time(), 'reset_id'=>'') );
2205  
2206              // Keep track of people logging in (and remove any failed logins 
2207              // for IP if any).
2208              $this->logins['succeeded'][] = array(
2209                  'username' => $username,
2210                  'time' => time(),
2211                  'ip' => $_SERVER['REMOTE_ADDR']
2212              );
2213              unset($this->logins['failed'][$_SERVER['REMOTE_ADDR']]);
2214              $this->saveLogins();
2215  
2216              $_SESSION['user']= $PIVOTX['users']->getUser($username);
2217  
2218              $path = $PIVOTX['paths']['site_url']; // Set to 'site url' instead of 'pivotx_url', because then we
2219                                          // can use 'edit this entry' and the like.
2220  
2221              if ($stay==1) {
2222  
2223                  $this->setCookie($this->cookie_name, $key);
2224  
2225              } else {
2226  
2227                  $this->setCookie($this->cookie_name, $key, 0);
2228  
2229              }
2230  
2231              $this->permsessions[ $key ] = array(
2232                  'username' => $username,
2233                  'ip' => $_SERVER['REMOTE_ADDR'],
2234                  'lastseen' => time()
2235              );
2236  
2237              $this->savePermsessions();
2238  
2239              return true;
2240          }
2241  
2242      }
2243  
2244      /**
2245       * Logs failed login attempts so PivotX can block brute force attacks.
2246       * 
2247       */
2248      function logFailedLogin() {
2249          global $PIVOTX;
2250  
2251          $ip = $_SERVER['REMOTE_ADDR'];
2252          
2253  
2254          $this->logins['failed'][ $ip ] = array(
2255            'attempts' => $this->logins['failed'][ $ip ]['attempts'] + 1,
2256            'time' => mktime()      
2257          );
2258              
2259          $this->saveLogins();
2260          debug(sprintf(__("Failed login attempt from '%s'."), $_SERVER['REMOTE_ADDR']));
2261      }
2262  
2263      /**
2264       * Checks failed login attempts so PivotX can block brute force attacks.
2265       * 
2266       */
2267      function checkFailedLogins() {
2268          global $PIVOTX;
2269          
2270          $limit = getDefault($PIVOTX['config']->get('failed_logins_limit'), 8);
2271          $ip = $_SERVER['REMOTE_ADDR'];
2272          
2273          if ($this->logins['failed'][ $ip ]['attempts'] > $limit) {
2274              return false;
2275          } else {
2276              return true;
2277          }
2278      }
2279  
2280      /**
2281       * Log out a user: clear the session, and delete the cookie
2282       *
2283       */
2284      function logout() {
2285          global $PIVOTX;
2286  
2287          $this->loadPermsessions();
2288  
2289          // remove current session (by username, so if the user logs out on
2290          // one location, he logs out everywhere)..
2291          foreach ($this->permsessions as $key => $session) {
2292              if ($session['username']==$_SESSION['user']['username']) {
2293                  unset($this->permsessions[$key]);
2294              }
2295          }
2296  
2297          $PIVOTX['events']->add('logout');
2298          $this->savePermsessions();
2299  
2300          // End the session..
2301          unset($_SESSION['user']);
2302          $this->setCookie($this->cookie_name, '', time()-10000 );
2303  
2304          session_destroy();
2305  
2306      }
2307  
2308      /**
2309       * Returns the latest/current message.
2310       *
2311       * @return array
2312       */
2313      function getMessage() {
2314  
2315          return $this->message;
2316  
2317      }
2318  
2319      /**
2320       * Returns the current user.
2321       *
2322       * @return array
2323       */
2324      function currentUser() {
2325  
2326          return $_SESSION['user'];
2327  
2328      }
2329  
2330  
2331      /**
2332       * Sets the specifics for the current user..
2333       *
2334       * @param array $user
2335       */
2336      function setUser($user) {
2337  
2338          $_SESSION['user'] = $user;
2339  
2340      }
2341  
2342  
2343      /**
2344       * Returns the username of the current user.
2345       *
2346       * @return array
2347       */
2348      function currentUsername() {
2349  
2350          return $_SESSION['user']['username'];
2351  
2352      }
2353  
2354  
2355      /**
2356       * Checks if the currently logged in user has at least the required level
2357       * to view the page he/she is trying to access.
2358       *
2359       * If not, the user is logged out of the system.
2360       *
2361       * @param int $level
2362       */
2363      function minLevel($level) {
2364          global $PIVOTX;
2365  
2366          $this->isLoggedIn();
2367  
2368          if ($level>$_SESSION['user']['userlevel']) {
2369              debug("logged out because the user's level was too low, or not logged in at all");
2370              
2371              // If $PIVOTX['paths'] is set and the headers aren't sent yet, redirect
2372              // to the login page, via the logout page.
2373              if (!empty($PIVOTX['paths']['pivotx_url']) && !headers_sent() ) {
2374                  header("Location: ". $PIVOTX['paths']['pivotx_url']."?page=logout");
2375              } else {
2376                  // otherwise, just display it, as good as we can. 
2377                  pageLogout();
2378              }
2379              die();                    
2380          }
2381  
2382      }
2383  
2384  
2385      /**
2386       * Checks if the current request is accompanied by the correct
2387       * CSRF check.
2388       *
2389       * If not, the user is logged out of the system.
2390       *
2391       * @param int $value
2392       */
2393      function checkCSRF($value) {
2394  
2395          if ($value != $_SESSION['pivotxsession']) {
2396              debug( sprintf("CSRF check failed: '%s..' vs. '%s..'",
2397                  substr($value,0,8), substr($_SESSION['pivotxsession'],0,8) ));
2398              pageLogout();
2399              die();
2400          }
2401  
2402      }
2403  
2404      /**
2405       * Get the key to use in the CSRF checks.
2406       *
2407       */
2408      function getCSRF() {
2409  
2410          return $_SESSION['pivotxsession'];
2411  
2412      }
2413  
2414  
2415      /**
2416       * Save permanent sessions to the filesystem, for users that check 'keep
2417       * me logged in'.
2418       *
2419       * The sessions are saved in db/ser_sessions.php, and they look somewhat like
2420       * Array
2421       * (
2422       *     [8nkvr62i3s37] => Array
2423       *         (
2424       *             [username] => admin
2425       *             [ip] => 127.0.0.1
2426       *             [lastseen] => 1168177821
2427       *         )
2428       * )
2429       *
2430       */
2431      function savePermsessions() {
2432          global $PIVOTX;
2433  
2434          saveSerialize($PIVOTX['paths']['db_path'] . "ser_sessions.php", $this->permsessions);
2435  
2436      }
2437  
2438  
2439      /**
2440       * Load the permanent sessions from the filesystem.
2441       *
2442       */
2443      function loadPermsessions() {
2444          global $PIVOTX;
2445  
2446          $this->permsessions = loadSerialize($PIVOTX['paths']['db_path'] . "ser_sessions.php", true);
2447  
2448          // Remove stale sessions after loading.
2449          foreach ($this->permsessions as $key=>$session) {
2450              if(($session['lastseen']+ $this->cookie_lifespan) < time() ) {
2451                  unset($this->permsessions[$key]);
2452              }
2453          }
2454  
2455      }
2456  
2457      /**
2458       * Save login attempts from the filesystem.
2459       */
2460      function saveLogins() {
2461          global $PIVOTX;
2462  
2463          // Trim the logins log, if it's too long.
2464          if (count($this->logins['failed']) > $this->maxlogins) {
2465              $this->logins['failed'] = array_slice($this->logins['failed'], -$this->maxlogins);
2466          }
2467          if (count($this->logins['succeeded']) > $this->maxlogins) {
2468              $this->logins['succeeded'] = array_slice($this->logins['succeeded'], -$this->maxlogins);
2469          }
2470  
2471          saveSerialize($PIVOTX['paths']['db_path'] . "ser_logins.php", $this->logins);
2472  
2473      }
2474  
2475  
2476      /**
2477       * Load stored login attempts from the filesystem.
2478       */
2479      function loadLogins() {
2480          global $PIVOTX;
2481  
2482          $timeout = getDefault($PIVOTX['config']->get('failed_logins_timeout'), 24);
2483      
2484          $this->logins = loadSerialize($PIVOTX['paths']['db_path'] . "ser_logins.php", true);
2485  
2486          // Set timeout to the timestamp at which the block needs to be dropped.
2487          $timeout = mktime() - ($timeout*3600);
2488  
2489          // Iterate over the failed attempts, to see if they need to be dropped.
2490          foreach ($this->logins['failed'] as $ip => $item) {
2491              if ($item['time']<$timeout) {
2492                  unset($this->logins['failed'][$ip]);
2493              }
2494          }
2495  
2496      }
2497  
2498  
2499      /**
2500       * Sets a session value, and then saves it.
2501       *
2502       * @param string $key
2503       * @param unknown_type $value
2504       */
2505      function setValue($key, $value=false) {
2506          if($value) {
2507              $_SESSION[$key] = $value;
2508          } else {
2509              unset($_SESSION[$key]);
2510              
2511          }
2512  
2513      }
2514  
2515  
2516      /**
2517       * Gets a single session value
2518       *
2519       * @param string $key
2520       * @return string
2521       */
2522      function getValue($key) {
2523          return $_SESSION[$key];
2524  
2525      }
2526  }
2527  
2528  
2529  
2530  
2531  /**
2532   * This class deals with Pages.
2533   *
2534   */
2535  class Pages {
2536  
2537      var $index;
2538      var $currentpage;
2539  
2540      /**
2541       * Initialisation
2542       *
2543       * @return Pages
2544       */
2545      function Pages() {
2546          global $PIVOTX;
2547  
2548          if ($PIVOTX['config']->get('db_model')=="flat") {
2549              require_once ("modules/pages_flat.php");
2550              $this->db = new PagesFlat();
2551          } else if ( ($PIVOTX['config']->get('db_model')=="mysql") ||
2552                  ($PIVOTX['config']->get('db_model')=="sqlite") ||
2553                  ($PIVOTX['config']->get('db_model')=="postgresql") ) {
2554              require_once ("modules/pages_sql.php");
2555              $this->db = new PagesSql();
2556          } else {
2557              // TODO: In case of a fatal error, we should give the user the chance to reset the
2558              // Config to the default state, and try again.
2559              die("Unknown DB Model! PivotX can not continue!");
2560          }
2561  
2562          $this->currentpage = array();
2563  
2564          $this->getIndex();
2565  
2566      }
2567  
2568      /**
2569       * Get the index of the available chapters and pages.
2570       *
2571       * @return array
2572       */
2573      function getIndex($excerpts=false, $links=false) {
2574          global $PIVOTX;
2575  
2576          $filteruser = "";
2577  
2578          // Check if we need to filter for a user, based on the 'show_only_own_userlevel'
2579          // settings.. We do this only when not rendering a weblog, otherwise the
2580          // pages that are filtered out won't show up on the site. 
2581          if (!defined('PIVOTX_INWEBLOG')) {
2582              $currentuser = $PIVOTX['users']->getUser( $PIVOTX['session']->currentUsername() );
2583              $currentuserlevel = (!$currentuser?1:$currentuser['userlevel']);
2584      
2585              if ( $currentuserlevel <= $PIVOTX['config']->get('show_only_own_userlevel') ) {
2586                  $filteruser = $currentuser['username'];
2587              }    
2588          }
2589  
2590          $this->index = $this->db->getIndex($filteruser, $excerpts, $links);
2591  
2592          return $this->index;
2593  
2594      }
2595  
2596      /**
2597       * Get the information for a specific Chapter
2598       *
2599       * @param integer $id
2600       * @return array
2601       */
2602      function getChapter($id) {
2603  
2604          return $this->index[$id];
2605  
2606      }
2607  
2608      /**
2609       * Add a chapter, and save the index
2610       *
2611       * @param array $chapter
2612       */
2613      function addChapter($chapter) {
2614  
2615          $this->index = $this->db->addChapter($chapter);
2616  
2617          $this->saveIndex(false);
2618  
2619      }
2620  
2621  
2622      /**
2623       * Delete a chapter, and save the index
2624       *
2625       * @param integer $uid
2626       */
2627      function delChapter($uid) {
2628  
2629          $this->index = $this->db->delChapter($uid);
2630  
2631          $this->saveIndex(false);
2632  
2633      }
2634  
2635  
2636      /**
2637       * Update the information for a chapter, and save the index
2638       *
2639       * @param integer $uid
2640       * @param array $chapter
2641       */
2642      function updateChapter($uid,$chapter) {
2643  
2644          $this->index = $this->db->updateChapter($uid,$chapter);
2645  
2646          $this->saveIndex(false);
2647      }
2648  
2649  
2650      /**
2651       * Save the index to the DB, using the selected model.
2652       *
2653       * @param boolean $reindex
2654       */
2655      function saveIndex($reindex=true) {
2656  
2657          uasort($this->index, array($this, 'chapSort'));
2658  
2659          $this->db->setIndex($this->index);
2660  
2661          $this->db->saveIndex($reindex);
2662  
2663      }
2664  
2665  
2666      /**
2667       * Get a single page
2668       *
2669       * @param integer $uid
2670       * @return array
2671       */
2672      function getPage($uid, $language=false) {
2673  
2674          $page = $this->db->getPage($uid,$language);
2675  
2676          $this->currentpage = $page;
2677  
2678          return $page;
2679  
2680      }
2681  
2682      /**
2683       * Get a single page in all languages
2684       *
2685       * @param integer $uid
2686       * @return array
2687       */
2688      function getPageInAllLanguages($uid) {
2689  
2690          if (method_exists($this->db,'getPageInAllLanguages')) {
2691              $page = $this->db->getPageInAllLanguages($uid);
2692          }
2693          else {
2694              $page = $this->db->getPage($uid);
2695          }
2696  
2697          $this->currentpage = $page;
2698  
2699          return $page;
2700  
2701      }
2702  
2703      /**
2704       * Get a single page, as defined by its URI
2705       *
2706       * @param string $uri
2707       * @return array
2708       */
2709      function getPageByUri($uri,$language=false) {
2710  
2711          $page = $this->db->getPageByUri($uri,$language);
2712  
2713          $this->currentpage = $page;
2714          
2715          return $page;
2716  
2717      }
2718  
2719      /**
2720       * Gets the current page
2721       */
2722      function getCurrentPage() {
2723          
2724          return $this->currentpage;
2725          
2726      }
2727  
2728      /**
2729       * Gets the $amount latest pages as an array, suitable for displaying an
2730       * overview
2731       *
2732       * @param integer $amount
2733       */
2734      function getLatestPages($amount, $filter_user="") {
2735  
2736          $pages = $this->db->getLatestPages($amount, $filter_user);
2737  
2738          return $pages;
2739  
2740      }
2741  
2742      /**
2743       * Delete a single page
2744       *
2745       * @param integer $uid
2746       */
2747      function delPage($uid) {
2748  
2749          $this->db->delPage($uid);
2750  
2751      }
2752  
2753  
2754      /**
2755       * Save a single page. Returns the uid of the inserted page.
2756       *
2757       * @param array $page
2758       * @return integer.
2759       */
2760      function savePage($page) {
2761  
2762          $this->currentpage = $page;
2763  
2764          if (method_exists($this->db,'savePageInAllLanguages')) {
2765              return $this->db->savePageInAllLanguages($page);
2766          }
2767  
2768          return $this->db->savePage($page);
2769      
2770  
2771      }
2772  
2773      /**
2774       * Sort the chapters based on the order and string comparison
2775       * of chapter name if order is identical.
2776       *
2777       * @param array $a
2778       * @param array $b
2779       * @return int
2780       */
2781      function chapSort($a, $b) {
2782          global $PIVOTX;
2783  
2784          if ($PIVOTX['config']->get('sort_chapters_by_alphabet')==true) {
2785              // If we set 'sort_chapters_by_alphabet' to true, always sort by alphabet..
2786              return strcmp($a['chaptername'],$b['chaptername']);
2787          } else if ($a['sortorder'] == $b['sortorder']) {
2788              // Else sort by alphabet, if order is the same..
2789              return strcmp($a['chaptername'],$b['chaptername']);
2790          } else {
2791              // else sort by order..
2792              return ($a['sortorder'] < $b['sortorder']) ? -1 : 1;
2793          }
2794  
2795      }
2796  
2797  
2798      /**
2799       * Checks if any pages set to 'timed publish' should be published.
2800       *
2801       */
2802      function checkTimedPublish() {
2803          $this->db->checkTimedPublish();
2804      }
2805      
2806  }
2807  
2808  
2809  
2810  /**
2811   * The class that does the work for the paging and paging_subweblog snippets.
2812   *
2813   * @author Hans Fredrik Nordhaug <hansfn@gmail.com>, The PivotX dev. Team.
2814   */
2815  class Paging {
2816      var $offset;
2817      var $name;
2818  
2819      function Paging($name) {
2820          $this->name = $name;
2821      }
2822  
2823      function sanity_check($action) {
2824          global $PIVOTX;
2825          list($action,$dummy) = explode('|',$action);
2826          if (($action != "next") && ($action != "prev") &&
2827              ($action != "curr") && ($action != "digg")) {
2828              return "<!-- snippet {$this->name} error: unknow action '$action' -->\n";
2829          }
2830  
2831          // Only display the paging snippet on weblog pages
2832          $modifier = $PIVOTX['parser']->get('modifier');
2833          if (($modifier['action'] != 'weblog') || !empty($modifier['archive'])) {
2834              return "<!-- snippet {$this->name} ($action): only output on weblog pages -->\n";
2835          }
2836          return;
2837      }
2838  
2839      function setup() {
2840          // Determine the offset
2841          if (!isset($_GET['o'])) {
2842              $this->offset = 0;
2843          } elseif (is_numeric($_GET['o'])) {
2844              $this->offset = $_GET['o'];
2845          } else {
2846              return "<!-- snippet {$this->name} error: offset isn't numeric -->\n";
2847          }
2848  
2849  
2850          return;
2851      }
2852  
2853  
2854      function doit($action, $text, $cats, $amountperpage, $params) {
2855          global $PIVOTX;
2856  
2857          $Current_weblog = $PIVOTX['weblogs']->getCurrent();
2858          $modifier = $PIVOTX['parser']->get('modifier');
2859  
2860          // $amountperpage must be numeric, one or larger
2861          if (!is_numeric($amountperpage) || ($amountperpage<1)) {
2862              return "<!-- snippet {$this->name} error: invalid number of entries to skip ($amountperpage) -->\n";
2863          }
2864  
2865          // Preserving some query parameters
2866          $query = array();
2867          if (isset($_GET['w']) && (empty($_GET['rewrite']) || ($_GET['rewrite'] == 'offset'))) {
2868              $query['w'] = 'w=' . $_GET['w'];
2869          }
2870          if (isset($_GET['t'])) {
2871              $query['t'] = 't=' . $_GET['t'];
2872          }
2873          if (!empty($_GET['u'])) {
2874              $query['u'] = 'u=' . $_GET['u'];
2875          }
2876  
2877          // Setting the text for the links
2878          if ($action == "next") {
2879              $text = getDefault($params['format'], __("Next page")." &#187;" );
2880          } elseif ($action == "prev") {
2881              $text = getDefault($params['format'], "&#171; ".__("Previous page"));
2882          } elseif ($action == "digg") {
2883              $text_prev = getDefault($params['format_prev'], "&#171; ".__("Previous page"));
2884              $text_next = getDefault($params['format_next'], __("Next page")." &#187;" );
2885          } else {
2886              $text = getDefault($params['format'], __("Displaying entries %num_from%-%num_to% of %num_tot%") );
2887          }
2888  
2889          // Get the maximum amount of pages to show.
2890          $max_digg_pages = getDefault($params['maxpages'], 9);
2891  
2892          // Get the id to attach to the <ul> for Digg style navigation.
2893          $digg_id = getDefault($params['id'], "pages");
2894  
2895          // Start the real work.
2896          $eachcatshash = md5(implodeDeep("", $cats));
2897          
2898          if ($PIVOTX['cache']->get('paging', $eachcatshash)) {
2899              // Check if this is in our simple cache?
2900              list($temp_tot, $num_tot) = $PIVOTX['cache']->get('paging', $eachcatshash); 
2901          } else {
2902  
2903              // Get the total amount of entries. How we do this depends on the used DB-model..
2904              // What we do is we get the amount of entries for each item in $cats.
2905              // For example, let's say we have 10 entries per page and 90 entries in one subweblog, and
2906              // 65 in the other. In this case we don't need (90+65)/10 pages, but (max(90,65))/10 pages.
2907              if ($PIVOTX['db']->db_type == "flat" ) {
2908                  // Get the amount from the Flat files DB..
2909                  $tot = $PIVOTX['db']->get_entries_count();
2910                  foreach ($cats as $eachcats) {
2911                      if (!is_array($eachcats) && (trim($eachcats) == '')) {
2912                          continue;
2913                      }
2914                      $temp_tot = count($PIVOTX['db']->read_entries(array(
2915                          'show'=>$tot, 'cats'=>$eachcats, 'user'=>$_GET['u'], 'status'=>'publish')));
2916                      $num_tot = max( $num_tot, $temp_tot);
2917                  }
2918              } else {
2919                  // Get the amount from our SQL db..
2920                  $sql = new Sql('mysql', $PIVOTX['config']->get('db_databasename'), $PIVOTX['config']->get('db_hostname'),
2921                  $PIVOTX['config']->get('db_username'), $PIVOTX['config']->get('db_password'));
2922                  $entriestable = safeString($PIVOTX['config']->get('db_prefix')."entries", true);
2923                  $categoriestable = safeString($PIVOTX['config']->get('db_prefix')."categories", true);
2924  
2925                      foreach ($cats as $eachcats) {
2926                          if (is_array($eachcats)) {
2927                              $eachcats = implode("','", $eachcats);
2928                          } else if (trim($eachcats) == '') { 
2929                              continue; 
2930                          }
2931                          $sql->query("SELECT COUNT(DISTINCT(e.uid)) FROM $entriestable AS e, $categoriestable as c
2932                          WHERE e.status='publish' AND e.uid=c.target_uid AND c.category IN ('$eachcats');");
2933                          $temp_tot = current($sql->fetch_row());
2934                          $num_tot = max( $num_tot, $temp_tot);
2935                  }
2936              }
2937              $PIVOTX['cache']->set('paging', $eachcatshash, array($temp_tot, $num_tot));
2938          }
2939  
2940          $offset = intval($modifier['offset']);
2941          $num_pages = ceil($num_tot / $amountperpage);
2942  
2943          if ($num_tot == 0) {
2944              return "<!-- snippet {$this->name}: no entries -->\n";
2945          } elseif ($offset >= $num_pages) {
2946              return "<!-- snippet {$this->name}: no more entries -->\n";
2947          }
2948  
2949          if ($action == "next") {
2950  
2951              $offset++;
2952  
2953              if ($offset >= $num_pages) {
2954                  return "<!-- snippet {$this->name} (next): no more entries -->\n";
2955              }
2956  
2957          } elseif ($action == "prev")  {
2958  
2959              if ($offset == 0) {
2960                  return "<!-- snippet {$this->name} (previous): no previous entries -->\n";
2961              } else {
2962                  $offset--;
2963              }
2964  
2965          } else {
2966              if ($num_tot == 0) {
2967                  return "<!-- snippet {$this->name} (curr): no current entries -->\n";
2968              } else {
2969                  $num = min($num,$num_tot);
2970              }
2971  
2972          }
2973  
2974          $num_from = $offset * $amountperpage + 1;
2975          $num_to = min($num_tot, ($offset+1) * $amountperpage);
2976  
2977          $text = str_replace("%num%", min($num_tot, $amountperpage), $text);
2978          $text = str_replace("%num_tot%", $num_tot, $text);
2979          $text = str_replace("%num_from%", $num_from, $text);
2980          $text = str_replace("%num_to%", $num_to, $text);
2981  
2982          if ($action == "curr") {
2983              return $text;
2984          }
2985  
2986          $site_url = getDefault($PIVOTX['weblogs']->get($Current_weblog, 'site_url'), $PIVOTX['paths']['site_url']);
2987          
2988          if ( (!empty($modifier['category']) || $params['catsinlink']==true) && $params['category']!="*" ) {
2989              // Ensure that we get a sorted list of unique categories in 
2990              // the URL - better SEO, one unique URL.
2991              $catslink = implodeDeep(",",$cats);
2992              $catslink = array_unique(explode(",",$catslink));
2993              sort($catslink, SORT_STRING);
2994              $catslink = implode(",",$catslink);
2995          }
2996   
2997          if ($PIVOTX['config']->get('mod_rewrite')==0) {
2998              if ( (!empty($modifier['category']) || $params['catsinlink']==true) && $params['category']!="*" ) {
2999                  $link = $site_url . "?c=" . $catslink . "&amp;o=";
3000              } else {
3001                  $link = $site_url . "?o=";
3002              }
3003          } else {
3004              if ( (!empty($modifier['category']) || $params['catsinlink']==true) && $params['category']!="*" ) {
3005                  $categoryname = getDefault( $PIVOTX['config']->get('localised_category_prefix'), "category");
3006                  $link = $site_url . $categoryname . "/" . $catslink . "/";
3007              } else {
3008                  $pagesname = getDefault( $PIVOTX['config']->get('localised_browse_prefix'), "browse");
3009                  $link = $site_url. $pagesname . "/";
3010              }
3011          }
3012  
3013  
3014          if ($action == 'digg') {
3015              $link .= '%offset%';
3016          } else {
3017              $link .= $offset;
3018          }
3019  
3020          if (!isset($query['w']) && paraWeblogNeeded($Current_weblog)) {
3021              if ($PIVOTX['config']->get('mod_rewrite')==0) {
3022                  $link .= "&amp;w=".para_weblog($Current_weblog);
3023              } else {
3024                  $link .= "/".para_weblog($Current_weblog);
3025              }
3026          }
3027  
3028          // Add the query parameters (if any)
3029          if (count($query) > 0) {
3030              $query = implode('&amp;', $query);
3031              if ($PIVOTX['config']->get('mod_rewrite')==0) {
3032                  $link .= '&amp;' . $query;
3033              } else {
3034                  $link .= '?' . $query;
3035              }
3036          }
3037  
3038          $link = str_replace(array('"',"'"), "", $link);
3039  
3040          if ($action != 'digg') {
3041  
3042              // Perhaps add some extra attributes to the <a> tag
3043              $extra = "";
3044              if (!empty($params['target'])) { $extra .= " target='" . $params['target'] ."'"; }
3045              if (!empty($params['class'])) { $extra .= " class='" . $params['class'] ."'"; }
3046              if (!empty($params['id'])) { $extra .= " id='" . $params['id'] ."'"; }
3047              if (!empty($params['datarole'])) { $extra .= " data-role='" . $params['datarole'] ."'"; }
3048  
3049              $output = sprintf('<a href="%s" %s>%s</a>', $link, $extra, $text);
3050  
3051              return $output;
3052  
3053          } else {
3054  
3055              $output ="
3056  <div id=\"{$digg_id}\">
3057      <ul>
3058      %links%
3059      </ul>
3060  </div>";
3061              $links = '';
3062  
3063              // Adding the previous link
3064              if ($offset == 0) {
3065                  $links .= '<li class="nolink">%text_prev%</li>';
3066              } else {
3067                  $links .= '<li><a href="%url%">%text_prev%</a></li>';
3068  
3069                  $url = str_replace('%offset%',max(0,$offset-1),$link);
3070                  $links = str_replace('%url%',$url,$links);
3071              }
3072  
3073              if ($num_pages > $max_digg_pages ) {
3074                  // Limit the number of links/listed pages.
3075  
3076                  $max_digg_pages = intval($max_digg_pages);
3077  
3078                  $start = (int) ($offset - 0.5 * ($max_digg_pages-1));
3079                  $start = max(0,$start) + 1;
3080                  $stop = (int) ($offset + 0.5 * ($max_digg_pages-1));
3081                  $stop = max(min(1000,$stop),3);
3082                  $page = $offset;
3083  
3084                  if ($offset==0) {
3085                      $links .= '<li class="current">1</li>';
3086                  } else if ($start>=1) {
3087                      $links .= '<li><a href="%url%">1</a></li>';
3088                      if ($start>=2) {
3089                          $links .= '<li class="skip">&#8230;</li>';
3090                      }
3091                      $url = str_replace('%offset%',0,$link);
3092                      $links = str_replace('%url%',$url,$links);
3093                  }
3094              } else {
3095                  // Display all links/listed pages.
3096                  $start = 0;
3097                  $stop = 100;
3098              }
3099  
3100  
3101              // Adding all links before the current page
3102              while ($start < $offset) {
3103                  $links .= '<li><a href="%url%">'.($start+1).'</a></li>';
3104                  $url = str_replace('%offset%', $start, $link);
3105                  $links = str_replace('%url%', $url, $links);
3106                  $start++;
3107              }
3108  
3109              // Current page..
3110              if ($start == $offset) {
3111                  $links .= '<li class="current">' . ($start+1) . '</li>';
3112                  $start++;
3113              }
3114  
3115              // Adding all links after the current page
3116              while ($start < $num_pages) {
3117                  if ($start < $stop) {
3118                      $links .= '<li><a href="%url%">'.($start+1).'</a></li>';
3119                      $url = str_replace('%offset%', $start, $link);
3120                      $links = str_replace('%url%', $url, $links);
3121                  } else if ($start == ($num_pages-2) ) {
3122                      $links .= '<li class="skip">&#8230;</li>';
3123                  } else if ($start == ($num_pages-1) ) {
3124                      $links .= '<li><a href="%url%">'.($start+1).'</a></li>';
3125                      $url = str_replace('%offset%', $start, $link);
3126                      $links = str_replace('%url%', $url, $links);
3127                  }
3128                  $page++;
3129                  $start++;
3130              }
3131  
3132  
3133              // Adding the next link
3134              if ( ($offset+1) >= $num_pages) {
3135                  $links .= '<li class="nolink">%text_next%</li>';
3136              } else {
3137                  $links .= '<li><a href="%url%">%text_next%</a></li>';
3138                  $url = str_replace('%offset%', $offset + 1, $link);
3139                  $links = str_replace('%url%', $url, $links);
3140              }
3141              $output = str_replace('%links%', $links, $output);
3142              $output = str_replace('%text_prev%', $text_prev, $output);
3143              $output = str_replace('%text_next%', $text_next, $output);
3144              return $output;
3145          }
3146      }
3147  
3148  }
3149  
3150  
3151  /**
3152   * A Class that provides for very simple, in-memory caching. 
3153   *
3154   * @author Bob, The PivotX dev. Team.
3155   */
3156  class Simplecache {
3157      
3158      var $cache;
3159      var $stats;
3160      var $keepstats;
3161      var $itemlimit;
3162      var $memlimit;
3163  
3164      function SimpleCache() {
3165          global $PIVOTX;
3166          
3167          $this->cache = array();
3168          $this->stats = array(
3169              'hits' => 0,
3170              'misses' => 0,
3171              'items' => 0,
3172              'size' => 0,
3173              'flushed' => 0
3174          );
3175          
3176          // Set the maximum number of items in the simple cache.
3177          $this->itemlimit = 400;   
3178          
3179          // Set the maximum amount of memory available.
3180          $this->memlimit = ini_get('memory_limit');
3181          list($this->memlimit) = sscanf($this->memlimit, "%dM");
3182          if (!empty($this->memlimit)) {
3183              $this->memlimit = $this->memlimit * 1048576;
3184          } else {
3185              $this->memlimit = 64 * 1048576;
3186          }
3187       
3188          $this->keepstats = $PIVOTX['config']->get('debug_cachestats');
3189          
3190      }
3191      
3192      /**
3193       * Set a single item in the cache
3194       *
3195       * @param string $type
3196       * @param string $key
3197       * @param mixed $value
3198       * @return bool
3199       */
3200      function set($type="general", $key, $value) {
3201          
3202          // Check if the $type and $key are OK
3203          if (empty($key) || (!is_string($key) && !is_integer($key) ) || !is_string($type)) {
3204              // debug("Not Set: $type - $key");
3205              return false;
3206          }
3207          
3208          if ($this->keepstats) {
3209              $this->stats['sets'][$type][$key]++;
3210          }
3211          
3212          if (!isset($this->cache[$type][$key])) {
3213              $this->stats['items']++;
3214          }
3215          
3216          $this->cache[$type][$key] = $value;
3217          
3218          $this->trim();
3219          
3220          return true;
3221          
3222      }
3223  
3224      /**
3225       * Set multiple items in the cache
3226       *
3227       * @param string $type
3228       * @param array $values
3229       * @return bool
3230       */
3231      function setMultiple($type="general", $values) {
3232          
3233          // Check if the $type and $key are OK
3234          if (empty($values) || !is_array($values) || !is_string($type)) {
3235              return false;
3236          }
3237          
3238          foreach($values as $key=>$value) {
3239              $this->set($type, $key, $value);    
3240          }
3241          
3242          return true;
3243          
3244      }
3245  
3246      /**
3247       * Get a single item from the cache. Returns the value on success, or false
3248       * when it's a miss. So, storing booleans in the cache isn't very convenient.
3249       *
3250       * @param string $type
3251       * @param string $key
3252       * @return mixed
3253       */
3254      function get($type="general", $key) {
3255      
3256          if ($this->keepstats) {
3257              $this->stats['gets'][$type][$key]++;
3258          }
3259      
3260          if (!empty($this->cache[$type][$key])) {
3261              // debug("Get(hit): $type - $key");
3262              $this->stats['hits']++;
3263              return $this->cache[$type][$key];
3264          } else {
3265              // debug("Get(miss): $type - $key");
3266              $this->stats['misses']++;
3267              return false;
3268          }
3269      
3270      }
3271      
3272      
3273      /**
3274       * Trims the cache, if it's getting too large.
3275       */
3276      function trim() {
3277          
3278          // Get the percentage of used memory..
3279          if (function_exists('memory_get_usage')) { 
3280              $mem = memory_get_usage();
3281          }
3282          
3283          if (!empty($mem) ) {
3284              $percentage = $mem / $this->memlimit;
3285          } else {
3286              $percentage = 0;
3287          }
3288                  
3289          // check if we need to trim items. 
3290          if ( ($this->stats['items'] > $this->itemlimit) || ($percentage > 0.8) ) {
3291          
3292              reset($this->cache);
3293  
3294              // Remove one item from each cached type.
3295              // Note: we don't use foreach, because it uses more memory, which is
3296              // exactly what we don't want, if we have to trim the cache..
3297              while ($key = key($this->cache)) {
3298  
3299                  if ($this->keepstats) {
3300                      debug('Simple cache flush: $key - ' . key($this->cache[$key]) );
3301                  }
3302                  
3303                  array_shift($this->cache[$key]);
3304                  $this->stats['items']--;
3305                  $this->stats['flushed']++;
3306                  
3307                  next($this->cache);
3308              }
3309      
3310  
3311          }        
3312          
3313      }
3314      
3315      /**
3316       * Return some basic statistics for the cache..
3317       *
3318       * @return array
3319       */
3320      function stats() {
3321          
3322          $this->stats['size'] = strlen(serialize($this->cache));
3323          
3324          return $this->stats;
3325          
3326      }
3327      
3328      function clear() {
3329          $this->cache = array();
3330      }
3331  
3332  }
3333  
3334  class Minify {
3335      
3336      var $html;
3337      var $head;
3338      var $jsfiles;
3339      var $cssfiles;
3340      var $base;
3341      
3342      function Minify($html) {
3343          global $PIVOTX;
3344          
3345          $this->html = $html;
3346          
3347          // Set the base path..
3348          if (defined('PIVOTX_INWEBLOG')) {
3349              $this->base = $PIVOTX['paths']['site_url'];
3350          } else {
3351              $this->base = $PIVOTX['paths']['pivotx_url'];
3352          }
3353  
3354      }
3355      
3356      function minifyURLS() {
3357  
3358          // if the PHP version is too low, we return the HTML, without doing anything.
3359          if (!checkVersion(phpversion(), '5.1.6')) {
3360              debug('PHPversion too low to us Minify: ' . phpversion() );
3361              return $this->html;
3362          }
3363          
3364          $head = $this->_getHead();
3365          
3366          if (empty($this->head)) {
3367              debug("Couldn't find a <head> section to minify");
3368          } else {
3369              $this->_getScripts();
3370              $this->_minifyScriptURLs();        
3371          }
3372          
3373          $this->_getStylesheets();
3374          $this->_minifyStylesheetURLs();
3375  
3376          return $this->html;
3377      }
3378      
3379      /**
3380       * Get the head section.
3381       **/            
3382      function _getHead() {
3383  
3384          preg_match("/<head([^>]+)?".">.*?<\/head>/is", $this->html, $matches);
3385  
3386          if(!empty($matches[0])) {
3387  
3388              $head = $matches[0];
3389  
3390              // Pull out the comment blocks, so as to avoid touching conditional comments
3391              $head = preg_replace("/<!-- .*? -->/is", '', $head);        
3392          
3393          } else {         
3394              $head = "";
3395          }
3396          
3397          $this->head = $head;
3398          
3399      }    
3400      
3401      
3402      /**
3403       * Get the scripts from the head section.
3404       **/            
3405      function _getScripts() {
3406      
3407          $scripts = array();
3408          
3409          $regex = "/<script[^>]+type=(['\"])(text\/javascript)\\1([^>]+)?".">(.*)<\/script>/iUs";
3410          preg_match_all($regex, $this->head, $matches);
3411  
3412  
3413          if (!empty($matches[0])) {
3414              
3415              
3416              $scripts = $matches[0];
3417           
3418              // remove 'inline' js, and links to external resources..
3419              // We also skip files with an '?', because they have extra paremeters, indicating
3420              // that they are generated, so we shouldn't minify them.
3421              foreach ($scripts as $key => $script) {
3422                  preg_match('/src=([\'"])(.*)\1/iUs', $script, $res);
3423                  
3424                  $res = $res[2];
3425                  $ext = getExtension($res);
3426                  
3427                  if ( empty($res) || ($ext!="js") || (strpos($res, "ttp://")==1) || (strpos($res, "ttps://")==1) || (strpos($res, "?")>0) ) {
3428                      unset($scripts[$key]);
3429                      continue;
3430                  }
3431                  
3432                  
3433              }
3434              
3435          }
3436          
3437          $this->jsfiles = $scripts;
3438          
3439      }
3440      
3441      /**
3442       * convert the found js files into one minify-link..
3443       */
3444      function _minifyScriptURLs() {
3445          global $PIVOTX;
3446          
3447          $sources = array();
3448          
3449          foreach ($this->jsfiles as $jsfile) {
3450              preg_match('/src=([\'"])(.*)\1/iUs', $jsfile, $res);
3451             
3452              $res = $res[2]; 
3453              // Add file paths to relative URLs..
3454              if (strpos($res, "/") !== 0) {
3455                  $res = $this->base . $res;
3456              }
3457              $source = preg_replace('#'.$PIVOTX['paths']['site_url'].'#', '', $res, 1);
3458              // Only add a source once
3459              if (!in_array($source, $sources)) {
3460                  $sources[] = $source;
3461              }
3462          }
3463  
3464          
3465          if (!empty($sources)) {
3466  
3467              $minifylink = sprintf("<scr"."ipt type=\"text/javascript\" src=\"%sincludes/minify/?f=%s\"></scr"."ipt>" ,
3468                      $PIVOTX['paths']['pivotx_url'],
3469                      implode(",", $sources)
3470                  );
3471          
3472              // Replace the first link to PivotX JS file with the minify link and remove 
3473              // all other links to PivotX JS files:
3474              $this->html = str_replace(array_shift($this->jsfiles), $minifylink, $this->html);
3475              $this->html = str_replace($this->jsfiles, "", $this->html);
3476          
3477          }
3478          
3479      }
3480      
3481      
3482     /**
3483       * Get the stylesheets from the entire document.
3484       **/            
3485      function _getStylesheets() {
3486      
3487          $stylesheets = array();
3488          
3489          $regex = "/<link[^>]+text\/css[^>]+>/iUs";
3490          preg_match_all($regex, $this->html, $matches);
3491  
3492          if (!empty($matches[0])) {
3493              
3494              // remove links to external resources, and organize by 'media' type..
3495              foreach ($matches[0] as $key => $stylesheet) {
3496                  preg_match('/href=[\'"](.*)[\'"]/iUs', $stylesheet, $res);
3497                  
3498                  $href = $res[1];
3499                  $ext = getExtension($href);
3500                  
3501                  // We also skip files with an '?', because they have extra paremeters, indicating
3502                  // that they are generated, so we shouldn't minify them.                
3503                  if ( empty($href) || ($ext!="css") || (strpos($href, "ttp://")==1) || (strpos($href, "ttps://")==1) || (strpos($href, "?")>0) ) {
3504                      continue;
3505                  }
3506                  
3507                  preg_match('/media=[\'"](.*)[\'"]/iUs', $stylesheet, $res);
3508                  
3509                  $media = $res[1];
3510                  
3511                  if ( empty($media) || ($media=="screen") ) {
3512                      $stylesheets['screen'][] = $stylesheet;
3513                  } else {
3514                      $stylesheets[$media][] = $stylesheet;
3515                  }
3516                  
3517              }
3518              
3519          }
3520            
3521          $this->cssfiles = $stylesheets;
3522          
3523      }    
3524      
3525      
3526      /**
3527       * convert the found css files into one minify-link..
3528       */
3529      function _minifyStylesheetURLs() {
3530          global $PIVOTX;
3531  
3532          // Loop for each separate mediatype..
3533          foreach($this->cssfiles as $mediatype => $cssfiles) {
3534              
3535              $sources = array();
3536               
3537              foreach ($cssfiles as $cssfile) {
3538                  preg_match('/href=[\'"](.*)[\'"]/iUs', $cssfile, $res);
3539                   
3540                  // Add file paths to relative URLs..
3541                  if (strpos($res[1], "/") !== 0) {
3542                      $res[1] = $this->base . $res[1];
3543                  }                  
3544                  $source = preg_replace('#'.$PIVOTX['paths']['site_url'].'#', '', $res[1], 1);
3545                  // Only add a source once
3546                  if (!in_array($source, $sources)) {
3547                      $sources[] = $source;
3548                  }
3549   
3550              }
3551               
3552              if (!empty($sources)) {
3553             
3554              
3555                  $minifylink = sprintf('<link href="%sincludes/minify/?url=%s&amp;f=%s" ' .
3556                      ' rel="stylesheet" type="text/css" media="%s" />' ,
3557                          $PIVOTX['paths']['pivotx_url'],
3558                          substr($PIVOTX['paths']['site_url'],0,strlen($PIVOTX['paths']['site_url'])-1),
3559                          implode(",", $sources),
3560                          $mediatype
3561                      );
3562                 
3563                  // Replace the javascript links in the source with the minify-link:
3564                  $this->html = str_replace($cssfiles[0], $minifylink, $this->html);
3565              
3566                  foreach($cssfiles as $cssfile) {
3567                      $this->html = str_replace($cssfile, "", $this->html);
3568                  }
3569              
3570              }            
3571              
3572          }
3573           
3574      }
3575          
3576      /**
3577       * OutputSystem Filter Method
3578       */
3579      public static function osFilter($html)
3580      {
3581          $minify = new Minify($html);
3582          return $minify->minifyURLS();
3583      }
3584  }
3585  
3586  
3587  /**
3588   * Takes care of the systemwide events, such as "Mike logged in." or "Pablo changed
3589   * the config setting 'xxx'."
3590   *
3591   */
3592  class Events {
3593  
3594      var $data;
3595      var $filename;
3596      var $edit_timeout;
3597      var $maxevents;
3598  
3599      function Events() {
3600          global $PIVOTX;
3601          
3602          $this->filename = "ser_events.php";
3603          
3604          $this->edittimeout = 60;
3605          $this->maxevents = getDefault($PIVOTX['config']->get('eventlog_length'), 200);
3606          
3607          $this->data = loadSerialize($PIVOTX['paths']['db_path'] . $this->filename, true);
3608  
3609          // Make sure we have a proper $this->maxevents..
3610          if (intval($this->maxevents) < 10) {
3611              $this->maxevents = 200;
3612          }
3613  
3614          // Make sure $this->data is set.
3615          if (empty($this->data) || !is_array($this->data)) {
3616              $this->data = array();
3617          }
3618          
3619      }
3620  
3621      function add($what, $uid, $extrainfo="") {
3622          global $PIVOTX;
3623          
3624          $timestamp = formatDate("", "%year%-%month%-%day%-%hour24%-%minute%");
3625          if (defined('PIVOTX_INADMIN') || defined('PIVOTX_INAJAXHELPER')) {
3626              $user = $PIVOTX['users']->getUser( $PIVOTX['session']->currentUsername() );
3627              $username = $user['username'];
3628          } else {
3629              $username = __('A visitor');
3630          }
3631          
3632          $event = array($timestamp, $username, $what, $uid, $extrainfo);
3633  
3634          array_push($this->data, $event);
3635          
3636          $this->save();
3637          
3638      }
3639  
3640      function save() {
3641          global $PIVOTX;
3642  
3643          // Trim the event log, if it's too long.
3644          if (count($this->data) > ($this->maxevents+10)) {
3645              $this->data = array_slice($this->data, -$this->maxevents);
3646          }
3647  
3648          saveSerialize($PIVOTX['paths']['db_path'] . $this->filename, $this->data);
3649          
3650      }
3651  
3652  
3653      /**
3654       * Get the last $amount events..
3655       */
3656      function get($amount=8) {
3657          global $PIVOTX;
3658         
3659          for ($i = count($this->data)-1; ($i>0 && $amount>0) ; $i-- ) {
3660          
3661              $event = $this->data[$i];
3662              
3663              // If $event[3] holds more than one uid, implode it to a string for printing.
3664              if (is_array($event[3])) {
3665                  $event[3] = implode(", ", $event[3]);
3666              }
3667              
3668              $name = "<strong>" . $event[1] ."</strong>";
3669          
3670              $format = "";
3671              
3672              switch ($event[2]) {
3673                  
3674                  case 'edit_entry':
3675                      if (!$saved['entry'][$event[1]][$event[3]]) {
3676                          $format = sprintf( __("%s started editing entry '%s'."), $name, $event[4] );
3677                          $saved['entry'][$event[1]][$event[3]] = true;
3678                      }
3679                      break;
3680  
3681                  case 'edit_page':
3682                      if (!$saved['page'][$event[1]][$event[3]]) {
3683                          $format = sprintf( __("%s started editing page '%s'."), $name, $event[4] );
3684                          $saved['page'][$event[1]][$event[3]] = true;
3685                      }
3686                      break;
3687                  
3688                  case 'save_entry':
3689                      $saved['entry'][$event[1]][$event[3]] = true;
3690                      $format = sprintf( __("%s saved entry '%s'."), $name, $event[4] );
3691                      break;
3692                  
3693                  case 'save_page':
3694                      $saved['page'][$event[1]][$event[3]] = true;
3695                      $format = sprintf( __("%s saved page '%s'."), $name, $event[4] );
3696                      break;
3697                  
3698                  case 'login':
3699                      $format = sprintf( __("%s logged in."), $name );
3700                      break;
3701                  
3702                  case 'logout':
3703                      $format = sprintf( __("%s logged out."), $name );
3704                      break;
3705  
3706                  case 'failed_login':
3707                      $format = sprintf( __("Failed login attempt for '%s'."), $event[4] );
3708                      break;
3709                  
3710                  case 'edit_config':
3711                      $format = sprintf( __("%s edited the setting for '%s'."), $name, $event[3] );
3712                      break;                
3713                  
3714                  case 'add_weblog':
3715                      $format = sprintf( __("%s added weblog '%s'."), $name, $event[4] );
3716                      break;                
3717                  
3718                  case 'edit_weblog':
3719                      $format = sprintf( __("%s edited a weblog setting for '%s'."), $name, $event[4] );
3720                      break;                
3721                  
3722                  case 'delete_weblog':
3723                      $format = sprintf( __("%s deleted weblog '%s'."), $name, $event[4] );
3724                      break;                
3725                  
3726                  case 'save_file':
3727                      $format = sprintf( __("%s saved the file '%s'."), $name, $event[4] );
3728                      break;                
3729                  
3730                  case 'add_user':
3731                      $format = sprintf( __("%s added user '%s'."), $name, $event[4] );
3732                      break;                
3733                                  
3734                  case 'edit_user':
3735                      $format = sprintf( __("%s edited user '%s'."), $name, $event[4] );
3736                      break;                
3737                                  
3738                  case 'delete_user':
3739                      $format = sprintf( __("%s deleted user '%s'."), $name, $event[4] );
3740                      break;                
3741                                  
3742                  case 'edit_category':
3743                      $format = sprintf( __("%s edited category '%s'."), $name, $event[4] );
3744                      break;                
3745                                
3746                  case 'delete_category':
3747                      $format = sprintf( __("%s deleted category '%s'."), $name, $event[4] );
3748                      break;
3749                  
3750                  case 'add_chapter':
3751                      $format = sprintf( __("%s added chapter '%s'."), $name, $event[4] );
3752                      break;                
3753                                  
3754                  case 'edit_chapter':
3755                      $format = sprintf( __("%s edited chapter '%s'."), $name, $event[4] );
3756                      break;                     
3757                                
3758                  
3759                  default:
3760                      if (!empty($event[4])) {
3761                          // Note: should we add a specific format for generic events with four parameters?
3762                          // I think not. If it's important enough, we should add a specific notice.
3763                          $format = sprintf( __("%s did '%s' on '%s'."), $name, $event[2], $event[4] );
3764                      } else if (!empty($event[3])) {
3765                          $format = sprintf( __("%s did '%s' on '%s'."), $name, $event[2], $event[3] );
3766                      } else {
3767                          $format = sprintf( __("%s did '%s'."), $name, $event[2] );  
3768                      }  
3769                      break;
3770                  
3771              }
3772              
3773              
3774              if (!empty($format)) {
3775              
3776                  $output[] = sprintf("<acronym title=\"%s\">%s</acronym>: %s",
3777                          formatDate($event[0], $PIVOTX['config']->get('fulldate_format')),
3778                          formatDateFuzzy($event[0]),
3779                          $format
3780                      );
3781  
3782                  $amount--;
3783              }
3784              
3785          }
3786          
3787          return $output;
3788          
3789      }
3790  
3791  
3792  }
3793  
3794  ?>


Generated: Mon May 21 01:08:32 2012 Cross-referenced by PHPXref 0.6