b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_core/_template.funcs.php - 2370 lines - 74276 bytes - Summary - Text - Print

Description: This file implements misc functions that handle output of the HTML page. This file is part of the evoCore framework - {@link http://evocore.net/} See also {@link http://sourceforge.net/projects/evocms/}.

   1  <?php
   2  /**
   3   * This file implements misc functions that handle output of the HTML page.
   4   *
   5   * This file is part of the evoCore framework - {@link http://evocore.net/}
   6   * See also {@link http://sourceforge.net/projects/evocms/}.
   7   *
   8   * @copyright (c)2003-2014 by Francois Planque - {@link http://fplanque.com/}
   9   * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
  10   *
  11   * {@internal License choice
  12   * - If you have received this file as part of a package, please find the license.txt file in
  13   *   the same folder or the closest folder above for complete license terms.
  14   * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  15   *   then you must choose one of the following licenses before using the file:
  16   *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  17   *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  18   * }}
  19   *
  20   * {@internal Open Source relicensing agreement:
  21   * Daniel HAHLER grants Francois PLANQUE the right to license
  22   * Daniel HAHLER's contributions to this file and the b2evolution project
  23   * under any OSI approved OSS license (http://www.opensource.org/licenses/).
  24   * }}
  25   *
  26   * @package evocore
  27   *
  28   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  29   * @author blueyed: Daniel HAHLER.
  30   * @author fplanque: Francois PLANQUE.
  31   *
  32   * @version $Id: _template.funcs.php 6136 2014-03-08 07:59:48Z manuel $
  33   */
  34  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  35  
  36  
  37  /**
  38   * Template tag. Output content-type header
  39   *
  40   * @param string content-type; override for RSS feeds
  41   */
  42  function header_content_type( $type = 'text/html', $charset = '#' )
  43  {
  44      global $io_charset;
  45      global $content_type_header;
  46  
  47      $content_type_header = 'Content-type: '.$type;
  48  
  49      if( !empty($charset) )
  50      {
  51          if( $charset == '#' )
  52          {
  53              $charset = $io_charset;
  54          }
  55  
  56          $content_type_header .= '; charset='.$charset;
  57      }
  58  
  59      header( $content_type_header );
  60  }
  61  
  62  
  63  /**
  64   * This is a placeholder for future development.
  65   *
  66   * @param string content-type; override for RSS feeds
  67   * @param integer seconds
  68   * @param string charset
  69   * @param boolean flush already collected content from the PageCache
  70   */
  71  function headers_content_mightcache( $type = 'text/html', $max_age = '#', $charset = '#', $flush_pagecache = true )
  72  {
  73      global $Messages, $is_admin_page;
  74      global $PageCache, $Debuglog;
  75  
  76      header_content_type( $type, $charset );
  77  
  78      if( empty($max_age) || $is_admin_page || is_logged_in() || $Messages->count() )
  79      {    // Don't cache if no max_age given
  80          // + NEVER EVER allow admin pages to cache
  81          // + NEVER EVER allow logged in data to be cached
  82          // + NEVER EVER allow transactional Messages to be cached!:
  83          header_nocache();
  84  
  85          // Check server caching too, but note that this is a different caching process then caching on the client
  86          // It's important that this is a double security check only and server caching should be prevented before this
  87          // If something should not be cached on the client, it should never be cached on the server either
  88          if( !empty( $PageCache ) )
  89          { // Abort PageCache collect
  90              $Debuglog->add( 'Abort server caching in headers_content_mightcache() function. This should have been prevented!' );
  91              $PageCache->abort_collect( $flush_pagecache );
  92          }
  93          return;
  94      }
  95  
  96      // If we are on a "normal" page, we may, under some circumstances, tell the browser it can cache the data.
  97      // This MAY be extremely confusing though, every time a user logs in and gets back to a screen with no evobar!
  98      // This cannot be enabled by default and requires admin switches.
  99  
 100      // For feeds, it is a little bit less confusing. We might want to have the param enabled by default in that case.
 101  
 102      // WARNING: extra special care needs to be taken before ever caching a blog page that might contain a form or a comment preview
 103      // having user details cached would be extremely bad.
 104  
 105      // in the meantime...
 106      header_nocache();
 107  }
 108  
 109  
 110  /**
 111   * Sends HTTP header to redirect to the previous location (which
 112   * can be given as function parameter, GET parameter (redirect_to),
 113   * is taken from {@link Hit::$referer} or {@link $baseurl}).
 114   *
 115   * {@link $Debuglog} and {@link $Messages} get stored in {@link $Session}, so they
 116   * are available after the redirect.
 117   *
 118   * NOTE: This function {@link exit() exits} the php script execution.
 119   *
 120   * @todo fp> do NOT allow $redirect_to = NULL. This leads to spaghetti code and unpredictable behavior.
 121   *
 122   * @param string Destination URL to redirect to
 123   * @param boolean|integer is this a permanent redirect? if true, send a 301; otherwise a 303 OR response code 301,302,303
 124   */
 125  function header_redirect( $redirect_to = NULL, $status = false )
 126  {
 127      /**
 128       * put your comment there...
 129       *
 130       * @var Hit
 131       */
 132      global $Hit;
 133      global $baseurl, $Blog, $htsrv_url_sensitive;
 134      global $Session, $Debuglog, $Messages;
 135      global $http_response_code;
 136  
 137      // TODO: fp> get this out to the caller, make a helper func like get_returnto_url()
 138      if( empty($redirect_to) )
 139      { // see if there's a redirect_to request param given:
 140          $redirect_to = param( 'redirect_to', 'url', '' );
 141  
 142          if( empty($redirect_to) )
 143          {
 144              if( ! empty($Hit->referer) )
 145              {
 146                  $redirect_to = $Hit->referer;
 147              }
 148              elseif( isset($Blog) && is_object($Blog) )
 149              {
 150                  $redirect_to = $Blog->get('url');
 151              }
 152              else
 153              {
 154                  $redirect_to = $baseurl;
 155              }
 156          }
 157          elseif( $redirect_to[0] == '/' )
 158          { // relative URL, prepend current host:
 159              global $ReqHost;
 160              $redirect_to = $ReqHost.$redirect_to;
 161          }
 162      }
 163      // <fp
 164  
 165      if( $redirect_to[0] == '/' )
 166      {
 167          // TODO: until all calls to header_redirect are cleaned up:
 168          global $ReqHost;
 169          $redirect_to = $ReqHost.$redirect_to;
 170          // debug_die( '$redirect_to must be an absolute URL' );
 171      }
 172  
 173      if( strpos($redirect_to, $htsrv_url_sensitive) === 0 /* we're going somewhere on $htsrv_url_sensitive */
 174       || strpos($redirect_to, $baseurl) === 0   /* we're going somewhere on $baseurl */ )
 175      {
 176          // Remove login and pwd parameters from URL, so that they do not trigger the login screen again:
 177          // Also remove "action" get param to avoid unwanted actions
 178          // blueyed> Removed the removing of "action" here, as it is used to trigger certain views. Instead, "confirm(ed)?" gets removed now
 179          // fp> which views please (important to list in order to remove asap)
 180          // dh> sorry, don't remember
 181          // TODO: fp> action should actually not be used to trigger views. This should be changed at some point.
 182          // TODO: fp> confirm should be normalized to confirmed
 183          $redirect_to = preg_replace( '~(?<=\?|&) (login|pwd|confirm(ed)?) = [^&]+ ~x', '', $redirect_to );
 184      }
 185  
 186      if( is_integer($status) )
 187      {
 188          $http_response_code = $status;
 189      }
 190      else
 191      {
 192          $http_response_code = $status ? 301 : 303;
 193      }
 194       $Debuglog->add('***** REDIRECT TO '.$redirect_to.' (status '.$http_response_code.') *****', 'request' );
 195  
 196      if( ! empty($Session) )
 197      {    // Session is required here
 198  
 199          // Transfer of Debuglog to next page:
 200          if( $Debuglog->count('all') )
 201          {    // Save Debuglog into Session, so that it's available after redirect (gets loaded by Session constructor):
 202              $sess_Debuglogs = $Session->get('Debuglogs');
 203              if( empty($sess_Debuglogs) )
 204              {
 205                  $sess_Debuglogs = array();
 206              }
 207  
 208              $sess_Debuglogs[] = $Debuglog;
 209              $Session->set( 'Debuglogs', $sess_Debuglogs, 60 /* expire in 60 seconds */ );
 210              // echo 'Passing Debuglog(s) to next page';
 211              // pre_dump( $sess_Debuglogs );
 212          }
 213  
 214          // Transfer of Messages to next page:
 215          if( $Messages->count() )
 216          {    // Set Messages into user's session, so they get restored on the next page (after redirect):
 217              $Session->set( 'Messages', $Messages );
 218           // echo 'Passing Messages to next page';
 219          }
 220  
 221          $Session->dbsave(); // If we don't save now, we run the risk that the redirect goes faster than the PHP script shutdown.
 222      }
 223  
 224      // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
 225      switch( $http_response_code )
 226      {
 227          case 301:
 228              // This should be a permanent move redirect!
 229              header_http_response( '301 Moved Permanently' );
 230              break;
 231  
 232          case 303:
 233              // This should be a "follow up" redirect
 234              // Note: Also see http://de3.php.net/manual/en/function.header.php#50588 and the other comments around
 235              header_http_response( '303 See Other' );
 236              break;
 237  
 238          case 302:
 239          default:
 240              header_http_response( '302 Found' );
 241      }
 242  
 243      // debug_die($redirect_to);
 244      if( headers_sent($filename, $line) )
 245      {
 246          debug_die( sprintf('Headers have already been sent in %s on line %d.', basename($filename), $line)
 247                          .'<br />Cannot <a href="'.htmlspecialchars($redirect_to).'">redirect</a>.' );
 248      }
 249      header( 'Location: '.$redirect_to, true, $http_response_code ); // explictly setting the status is required for (fast)cgi
 250      exit(0);
 251  }
 252  
 253  
 254  
 255  /**
 256   * Sends HTTP headers to avoid caching of the page at the browser level
 257   * (at least without revalidating with the server to make sure whether the content has changed or not).
 258   *
 259   * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 260   */
 261  function header_nocache( $timestamp = NULL )
 262  {
 263      global $servertimenow;
 264      if( empty($timestamp) )
 265      {
 266          $timestamp = $servertimenow;
 267      }
 268  
 269      header('Expires: '.gmdate('r',$timestamp));
 270      header('Last-Modified: '.gmdate('r',$timestamp));
 271      header('Cache-Control: no-cache, must-revalidate');
 272      header('Pragma: no-cache');
 273  }
 274  
 275  
 276  /**
 277   * This is to "force" (strongly suggest) caching.
 278   *
 279   * WARNING: use this only for STATIC content that does NOT depend on the current user.
 280   *
 281   * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 282   */
 283  function header_noexpire()
 284  {
 285      global $servertimenow;
 286      header('Expires: '.gmdate('r', $servertimenow + 31536000)); // 86400*365 (1 year)
 287  }
 288  
 289  
 290  /**
 291   * Generate an etag to identify the version of the current page.
 292   * We use this primarily to make a difference between the same page that has been generated for anonymous users
 293   * and a version that has been generated for a specific user.
 294   *
 295   * A common problem without this would be that when users log out, the page cache would tell them "304 Not Modified"
 296   * based on the date of the cache and then the browser would show a locally cached version of the page that includes
 297   * the evobar.
 298   *
 299   * When a specific user logs out, the browser will send back the Etag of the logged in version it got and we will
 300   * be able to detect that this is not a "304 Not Modified" case -> we will send back the anonymou version of the page.
 301   */
 302  function gen_current_page_etag()
 303  {
 304      global $current_User, $Messages;
 305  
 306      if( isset($current_User) )
 307      {
 308          $etag = 'user:'.$current_User->ID;
 309      }
 310      else
 311      {
 312          $etag = 'user:anon';
 313      }
 314  
 315      if( $Messages->count() )
 316      {    // This case has never been observed yet, but let's forward protect us against client side cached messages
 317          $etag .= '-msg:'.md5($Messages->get_string('',''));
 318      }
 319  
 320      return '"'.$etag.'"';
 321  }
 322  
 323  
 324  /**
 325   * This adds teh etag header
 326   *
 327   * @param string etag MUST be "quoted"
 328   */
 329  function header_etag( $etag )
 330  {
 331      header( 'ETag: '.$etag );
 332  }
 333  
 334  
 335  /**
 336   * Get global title matching filter params
 337   *
 338   * Outputs the title of the category when you load the page with <code>?cat=</code>
 339   * Display "Archive Directory" title if it has been requested
 340   * Display "Latest comments" title if these have been requested
 341   * Display "Statistics" title if these have been requested
 342   * Display "User profile" title if it has been requested
 343   *
 344   * @todo single month: Respect locales datefmt
 345   * @todo single post: posts do no get proper checking (wether they are in the requested blog or wether their permissions match user rights,
 346   * thus the title sometimes gets displayed even when it should not. We need to pre-query the ItemList instead!!
 347   * @todo make it complete with all possible params!
 348   *
 349   * @param array params
 350   *        - "auto_pilot": "seo_title": Use the SEO title autopilot. (Default: "none")
 351   */
 352  function get_request_title( $params = array() )
 353  {
 354      global $MainList, $preview, $disp, $action, $current_User, $Blog, $admin_url;
 355  
 356      $r = array();
 357  
 358      $params = array_merge( array(
 359              'auto_pilot'          => 'none',
 360              'title_before'        => '',
 361              'title_after'         => '',
 362              'title_none'          => '',
 363              'title_single_disp'   => true,
 364              'title_single_before' => '#',
 365              'title_single_after'  => '#',
 366              'title_page_disp'     => true,
 367              'title_page_before'   => '#',
 368              'title_page_after'    => '#',
 369              'glue'                => ' - ',
 370              'format'              => 'htmlbody',
 371              'arcdir_text'         => T_('Archive Directory'),
 372              'catdir_text'         => T_('Category Directory'),
 373              'mediaidx_text'       => T_('Photo Index'),
 374              'postidx_text'        => T_('Post Index'),
 375              'search_text'         => T_('Search'),
 376              'sitemap_text'        => T_('Site Map'),
 377              'msgform_text'        => T_('Sending a message'),
 378              'messages_text'       => T_('Messages'),
 379              'contacts_text'       => T_('Contacts'),
 380              'login_text'          => /* TRANS: trailing space = verb */ T_('Login '),
 381              'register_text'       => T_('Register'),
 382              'req_validatemail'    => T_('Account activation'),
 383              'account_activation'  => T_('Account activation'),
 384              'lostpassword_text'   => T_('Lost password?'),
 385              'profile_text'        => T_('User Profile'),
 386              'avatar_text'         => T_('Profile picture'),
 387              'pwdchange_text'      => T_('Password change'),
 388              'userprefs_text'      => T_('User preferences'),
 389              'user_text'           => T_('User: %s'),
 390              'users_text'          => T_('Users'),
 391              'closeaccount_text'   => T_('Close account'),
 392              'subs_text'           => T_('Notifications'),
 393              'comments_text'       => T_('Latest Comments'),
 394              'feedback-popup_text' => T_('Feedback'),
 395              'edit_text_create'    => T_('New post'),
 396              'edit_text_update'    => T_('Editing post'),
 397              'edit_text_copy'      => T_('Duplicating post'),
 398              'edit_comment_text'   => T_('Editing comment'),
 399              'front_text'          => '',        // We don't want to display a special title on the front page
 400              'posts_text'          => '#',
 401              'useritems_text'      => T_('User posts'),
 402              'usercomments_text'   => T_('User comments'),
 403          ), $params );
 404  
 405      if( $params['auto_pilot'] == 'seo_title' )
 406      {    // We want to use the SEO title autopilot. Do overrides:
 407          $params['format'] = 'htmlhead';
 408          $params['title_after'] = $params['glue'].$Blog->get('name');
 409          $params['title_single_after'] = '';
 410          $params['title_page_after'] = '';
 411          $params['title_none'] = $Blog->dget('name','htmlhead');
 412      }
 413  
 414  
 415      $before = $params['title_before'];
 416      $after = $params['title_after'];
 417  
 418      switch( $disp )
 419      {
 420          case 'arcdir':
 421              // We are requesting the archive directory:
 422              $r[] = $params['arcdir_text'];
 423              break;
 424  
 425          case 'catdir':
 426              // We are requesting the archive directory:
 427              $r[] = $params['catdir_text'];
 428              break;
 429  
 430          case 'mediaidx':
 431              $r[] = $params['mediaidx_text'];
 432              break;
 433  
 434          case 'postidx':
 435              $r[] = $params['postidx_text'];
 436              break;
 437  
 438          case 'sitemap':
 439              $r[] = $params['sitemap_text'];
 440              break;
 441  
 442          case 'search':
 443              $r[] = $params['search_text'];
 444              break;
 445  
 446          case 'comments':
 447              // We are requesting the latest comments:
 448              global $Item;
 449              if( isset( $Item ) )
 450              {
 451                  $r[] = sprintf( $params['comments_text'] . T_(' on %s'), $Item->get('title') );
 452              }
 453              else
 454              {
 455                  $r[] = $params['comments_text'];
 456              }
 457              break;
 458  
 459          case 'feedback-popup':
 460              // We are requesting the comments on a specific post:
 461              // Should be in first position
 462              $Item = & $MainList->get_by_idx( 0 );
 463              $r[] = sprintf( $params['feedback-popup_text'] . T_(' on %s'), $Item->get('title') );
 464              break;
 465  
 466          case 'profile':
 467              // We are requesting the user profile:
 468              $r[] = $params['profile_text'];
 469              break;
 470  
 471          case 'avatar':
 472              // We are requesting the user avatar:
 473              $r[] = $params['avatar_text'];
 474              break;
 475  
 476          case 'pwdchange':
 477              // We are requesting the user change password:
 478              $r[] = $params['pwdchange_text'];
 479              break;
 480  
 481          case 'userprefs':
 482              // We are requesting the user preferences:
 483              $r[] = $params['userprefs_text'];
 484              break;
 485  
 486          case 'subs':
 487              // We are requesting the subscriptions screen:
 488              $r[] = $params['subs_text'];
 489              break;
 490  
 491          case 'msgform':
 492              // We are requesting the message form:
 493              $r[] = $params['msgform_text'];
 494              break;
 495  
 496          case 'threads':
 497          case 'messages':
 498              // We are requesting the messages form
 499              $thrd_ID = param( 'thrd_ID', 'integer', 0 );
 500              if( empty( $thrd_ID ) )
 501              {
 502                  $r[] = $params['messages_text'];
 503              }
 504              else
 505              {    // We get a thread title by ID
 506                  load_class( 'messaging/model/_thread.class.php', 'Thread' );
 507                  $ThreadCache = & get_ThreadCache();
 508                  if( $Thread = $ThreadCache->get_by_ID( $thrd_ID, false ) )
 509                  {    // Thread exists and we get a title
 510                      if( $params['auto_pilot'] == 'seo_title' )
 511                      {    // Display thread title only for tag <title>
 512                          $r[] = $Thread->title;
 513                      }
 514                  }
 515                  else
 516                  {    // Bad request with not existing thread
 517                      $r[] = strip_tags( $params['messages_text'] );
 518                  }
 519              }
 520              break;
 521  
 522          case 'contacts':
 523              // We are requesting the message form:
 524              $r[] = $params['contacts_text'];
 525              break;
 526  
 527          case 'login':
 528              // We are requesting the login form:
 529              if( $action == 'req_validatemail' )
 530              {
 531                  $r[] = $params['req_validatemail'];
 532              }
 533              else
 534              {
 535                  $r[] = $params['login_text'];
 536              }
 537              break;
 538  
 539          case 'register':
 540              // We are requesting the registration form:
 541              $r[] = $params['register_text'];
 542              break;
 543  
 544          case 'activateinfo':
 545              // We are requesting the activate info form:
 546              $r[] = $params['account_activation'];
 547              break;
 548  
 549          case 'lostpassword':
 550              // We are requesting the lost password form:
 551              $r[] = $params['lostpassword_text'];
 552              break;
 553  
 554          case 'single':
 555          case 'page':
 556              // We are displaying a single message:
 557              if( $preview )
 558              {    // We are requesting a post preview:
 559                  $r[] = T_('PREVIEW');
 560              }
 561              elseif( $params['title_'.$disp.'_disp'] && isset( $MainList ) )
 562              {
 563                  $r = array_merge( $r, $MainList->get_filter_titles( array( 'visibility', 'hide_future' ), $params ) );
 564              }
 565              if( $params['title_'.$disp.'_before'] != '#' )
 566              {
 567                  $before = $params['title_'.$disp.'_before'];
 568              }
 569              if( $params['title_'.$disp.'_after'] != '#' )
 570              {
 571                  $after = $params['title_'.$disp.'_after'];
 572              }
 573              break;
 574  
 575          case 'user':
 576              // We are requesting the user page:
 577              $user_ID = param( 'user_ID', 'integer', 0 );
 578              $UserCache = & get_UserCache();
 579              $User = & $UserCache->get_by_ID( $user_ID, false, false );
 580              $user_login = $User ? $User->get( 'login' ) : '';
 581              $r[] = sprintf( $params['user_text'], $user_login );
 582              break;
 583  
 584          case 'users':
 585              $r[] = $params['users_text'];
 586              break;
 587  
 588          case 'closeaccount':
 589              $r[] = $params['closeaccount_text'];
 590              break;
 591  
 592          case 'edit':
 593              $action = param_action(); // Edit post by switching into 'In skin' mode from Back-office
 594              $p = param( 'p', 'integer', 0 ); // Edit post from Front-office
 595              $cp = param( 'cp', 'integer', 0 ); // Copy post from Front-office
 596              if( $action == 'edit_switchtab' || $p > 0 )
 597              {    // Edit post
 598                  $title = $params['edit_text_update'];
 599              }
 600              else if( $cp > 0 )
 601              {    // Copy post
 602                  $title = $params['edit_text_copy'];
 603              }
 604              else
 605              {    // Create post
 606                  $title = $params['edit_text_create'];
 607              }
 608              if( $params['auto_pilot'] != 'seo_title' )
 609              {    // Add advanced edit and close icon
 610                  global $edited_Item;
 611                  if( !empty( $edited_Item ) && $edited_Item->ID > 0 )
 612                  {    // Set the cancel editing url as permanent url of the item
 613                      $cancel_url = $edited_Item->get_permanent_url();
 614                  }
 615                  else
 616                  {    // Set the cancel editing url to home page of the blog
 617                      $cancel_url = $Blog->gen_blogurl();
 618                  }
 619  
 620                  $title .= '<span class="title_action_icons">';
 621                  if( $current_User->check_perm( 'admin', 'normal' ) )
 622                  {
 623                      global $advanced_edit_link;
 624                      $title .= action_icon( T_('Go to advanced edit screen'), 'edit', $advanced_edit_link['href'], ' '.T_('Advanced editing'), NULL, 3, array( 'onclick' => $advanced_edit_link['onclick'] ) );
 625                  }
 626                  $title .= action_icon( T_('Cancel editing'), 'close', $cancel_url, ' '.T_('Cancel editing'), NULL, 3 );
 627                  $title .= '</span>';
 628              }
 629              $r[] = $title;
 630              break;
 631  
 632          case 'edit_comment':
 633              global $comment_Item, $edited_Comment;
 634              $title = $params['edit_comment_text'];
 635              if( $params['auto_pilot'] != 'seo_title' )
 636              {    // Add advanced edit and close icon
 637                  $title .= '<span class="title_action_icons">';
 638                  if( $current_User->check_perm( 'admin', 'normal' ) )
 639                  {
 640                      $advanced_edit_url = url_add_param( $admin_url, 'ctrl=comments&amp;action=edit&amp;blog='.$Blog->ID.'&amp;comment_ID='.$edited_Comment->ID );
 641                      $title .= action_icon( T_('Go to advanced edit screen'), 'edit', $advanced_edit_url, ' '.T_('Advanced editing'), NULL, 3, array( 'onclick' => 'return switch_edit_view();' ) );
 642                  }
 643                  if( empty( $comment_Item ) )
 644                  {
 645                      $comment_Item = & $edited_Comment->get_Item();
 646                  }
 647                  if( !empty( $comment_Item ) )
 648                  {
 649                      $title .= action_icon( T_('Cancel editing'), 'close', url_add_tail( $comment_Item->get_permanent_url(), '#c'.$edited_Comment->ID ), ' '.T_('Cancel editing'), NULL, 3 );
 650                  }
 651                  $title .= '</span>';
 652              }
 653              $r[] = $title;
 654              break;
 655  
 656          case 'useritems':
 657              // We are requesting the user items list:
 658              $r[] = $params['useritems_text'];
 659              break;
 660  
 661          case 'usercomments':
 662              // We are requesting the user comments list:
 663              $r[] = $params['usercomments_text'];
 664              break;
 665  
 666          default:
 667              if( isset( $MainList ) )
 668              {
 669                  $r = array_merge( $r, $MainList->get_filter_titles( array( 'visibility', 'hide_future' ), $params ) );
 670              }
 671              break;
 672      }
 673  
 674  
 675      if( ! empty( $r ) )
 676      {    // We have at leats one title match:
 677          $r = implode( $params['glue'], $r );
 678          if( ! empty( $r ) )
 679          {    // This is in case we asked for an empty title (e-g for search)
 680              $r = $before.format_to_output( $r, $params['format'] ).$after;
 681          }
 682      }
 683      elseif( !empty( $params['title_none'] ) )
 684      {
 685          $r = $params['title_none'];
 686      }
 687      else
 688      {    // never return array()
 689          $r = '';
 690      }
 691  
 692      return $r;
 693  }
 694  
 695  
 696  /**
 697   * Display a global title matching filter params
 698   *
 699   * @param array params
 700   *        - "auto_pilot": "seo_title": Use the SEO title autopilot. (Default: "none")
 701   */
 702  function request_title( $params = array() )
 703  {
 704      $r = get_request_title( $params );
 705  
 706      if( !empty( $r ) )
 707      { // We have something to display:
 708          echo $r;
 709      }
 710  }
 711  
 712  
 713  /**
 714   * Returns a "<base />" tag and remembers that we've used it ({@link regenerate_url()} needs this).
 715   *
 716   * @param string URL to use (this gets used as base URL for all relative links on the HTML page)
 717   * @return string
 718   */
 719  function base_tag( $url, $target = NULL )
 720  {
 721      global $base_tag_set;
 722      $base_tag_set = $url;
 723      echo '<base href="'.$url.'"';
 724  
 725      if( !empty($target) )
 726      {
 727          echo ' target="'.$target.'"';
 728      }
 729      echo " />\n";
 730  }
 731  
 732  
 733  /**
 734   * Robots tag
 735   *
 736   * Outputs the robots meta tag if necessary
 737   */
 738  function robots_tag()
 739  {
 740      global $robots_index, $robots_follow;
 741  
 742      if( is_null($robots_index) && is_null($robots_follow) )
 743      {
 744          return;
 745      }
 746  
 747      $r = '<meta name="robots" content="';
 748  
 749      if( $robots_index === false )
 750          $r .= 'NOINDEX';
 751      else
 752          $r .= 'INDEX';
 753  
 754      $r .= ',';
 755  
 756      if( $robots_follow === false )
 757          $r .= 'NOFOLLOW';
 758      else
 759          $r .= 'FOLLOW';
 760  
 761      $r .= '" />'."\n";
 762  
 763      echo $r;
 764  }
 765  
 766  
 767  /**
 768   * Output a link to current blog.
 769   *
 770   * We need this function because if no Blog is currently active (some admin pages or site pages)
 771   * then we'll go to the general home.
 772   */
 773  function blog_home_link( $before = '', $after = '', $blog_text = 'Blog', $home_text = 'Home' )
 774  {
 775      global $Blog, $baseurl;
 776  
 777      if( !empty( $Blog ) )
 778      {
 779          echo $before.'<a href="'.$Blog->get( 'url' ).'">'.$blog_text.'</a>'.$after;
 780      }
 781      elseif( !empty($home_text) )
 782      {
 783          echo $before.'<a href="'.$baseurl.'">'.$home_text.'</a>'.$after;
 784      }
 785  }
 786  
 787  
 788  /**
 789   * Memorize that a specific javascript file will be required by the current page.
 790   * All requested files will be included in the page head only once (when headlines is called)
 791   *
 792   * Accepts absolute urls, filenames relative to the rsc/js directory and certain aliases, like 'jquery' and 'jquery_debug'
 793   * If 'jquery' is used and $debug is set to true, the 'jquery_debug' is automatically swapped in.
 794   * Any javascript added to the page is also added to the $required_js array, which is then checked to prevent adding the same code twice
 795   *
 796   * @todo dh>merge with require_css()
 797   * @param string alias, url or filename (relative to rsc/js) for javascript file
 798   * @param boolean|string Is the file's path relative to the base path/url?
 799   */
 800  function require_js( $js_file, $relative_to = 'rsc_url' )
 801  {
 802      global $rsc_url, $debug, $app_version;
 803      static $required_js;
 804  
 805      $js_aliases = array(
 806          '#jquery#' => 'jquery.min.js',
 807          '#jquery_debug#' => 'jquery.js',
 808          '#jqueryUI#' => 'jquery/jquery.ui.all.min.js',
 809          '#jqueryUI_debug#' => 'jquery/jquery.ui.all.js',
 810      );
 811  
 812      if( in_array( $js_file, array( '#jqueryUI#', '#jqueryUI_debug#' ) ) )
 813      {    // Dependency : ensure jQuery is loaded
 814          require_js( '#jquery#', $relative_to );
 815      }
 816      elseif( $js_file == 'communication.js' )
 817      { // jQuery dependency
 818          require_js( '#jquery#', $relative_to );
 819      }
 820  
 821      if( !empty( $js_aliases[$js_file]) )
 822      { // We are requsting an alias
 823          if ( $js_file == '#jquery#' && $debug )
 824          {
 825              $js_file = '#jquery_debug#';
 826          }
 827          $js_file = $js_aliases[$js_file];
 828  
 829          if( $relative_to === 'relative' || $relative_to === true )
 830          {    // Aliases cannot be relative, make it relative to $rsc_url
 831              $relative_to = 'rsc_url';
 832          }
 833      }
 834  
 835  
 836      if( $relative_to === 'relative' || $relative_to === true )
 837      {    // Make the file relative to current page <base>:
 838          $js_url = $js_file;
 839      }
 840      elseif( preg_match('~^https?://~', $js_file ) )
 841      { // It's an absolute url, keep it as is:
 842          $js_url = $js_file;
 843      }
 844      elseif( $relative_to === 'rsc_url' || $relative_to === false )
 845      {    // Get the file from $rsc_url:
 846          $js_url = $rsc_url.'js/'.$js_file;
 847      }
 848      elseif( $relative_to === 'blog' )
 849      {    // Get the file from $rsc_url:
 850          global $Blog;
 851          if( !empty( $Blog ) )
 852          {
 853              $js_url = $Blog->get_local_rsc_url().'js/'.$js_file;
 854          }
 855          else
 856          {
 857              $js_url = $rsc_url.'js/'.$js_file;
 858          }
 859      }
 860      else
 861      {
 862          debug_die('Unknown $relative to argument in require_js()');
 863      }
 864  
 865  
 866      // Be sure to get a fresh copy of this JS file after application upgrades:
 867      $js_url = url_add_param( $js_url, 'v='.$app_version );
 868  
 869      // Add to headlines, if not done already:
 870      if( empty( $required_js ) || ! in_array( strtolower($js_url), $required_js ) )
 871      {
 872          $required_js[] = strtolower($js_url);
 873          add_headline( '<script type="text/javascript" src="'.$js_url.'"></script>' );
 874      }
 875  }
 876  
 877  
 878  /**
 879   * Memorize that a specific css that file will be required by the current page.
 880   * All requested files will be included in the page head only once (when headlines is called)
 881   *
 882   * Accepts absolute urls, filenames relative to the rsc/css directory.
 883   * Set $relative_to_base to TRUE to prevent this function from adding on the rsc_path
 884   *
 885   * @todo dh>merge with require_js()
 886   * @param string alias, url or filename (relative to rsc/css) for CSS file
 887   * @param boolean|string Is the file's path relative to the base path/url?
 888   * @param string title.  The title for the link tag
 889   * @param string media.  ie, 'print'
 890   * @param string version number to append at the end of requested url to avoid getting an old version from the cache
 891   */
 892  function require_css( $css_file, $relative_to = 'rsc_url', $title = NULL, $media = NULL, $version = '#' )
 893  {
 894      global $rsc_url, $debug, $app_version;
 895      static $required_css;
 896  
 897      if( $relative_to === 'relative' || $relative_to === true )
 898      {    // Make the file relative to current page <base>:
 899          $css_url = $css_file;
 900      }
 901      elseif( preg_match('~^https?://~', $css_file ) )
 902      { // It's an absolute url, keep it as is:
 903          $css_url = $css_file;
 904      }
 905      elseif( $relative_to === 'rsc_url' || $relative_to === false )
 906      {    // Get the file from $rsc_url:
 907          $css_url = $rsc_url.'css/'.$css_file;
 908      }
 909      elseif( $relative_to === 'blog' )
 910      {    // Get the file from $rsc_url:
 911          global $Blog;
 912          $css_url = $Blog->get_local_rsc_url().'css/'.$css_file;
 913      }
 914      else
 915      {
 916          debug_die('Unknown $relative to argument in require_css()');
 917      }
 918  
 919      if( !empty($version) )
 920      {    // Be sure to get a fresh copy of this CSS file after application upgrades:
 921          if( $version == '#' )
 922          {
 923              $version = $app_version;
 924          }
 925          $css_url = url_add_param( $css_url, 'v='.$version );
 926      }
 927  
 928      // Add to headlines, if not done already:
 929      // fp> TODO: check for url without version to avoid duplicate load due to lack of verison in @import statements
 930      if( empty( $required_css ) || ! in_array( strtolower($css_url), $required_css ) )
 931      {
 932          $required_css[] = strtolower($css_url);
 933  
 934          $start_link_tag = '<link rel="stylesheet"';
 935          if ( !empty( $title ) ) $start_link_tag .= ' title="' . $title . '"';
 936          if ( !empty( $media ) ) $start_link_tag .= ' media="' . $media . '"';
 937          $start_link_tag .= ' type="text/css" href="';
 938          $end_link_tag = '" />';
 939          add_headline( $start_link_tag . $css_url . $end_link_tag );
 940      }
 941  
 942  }
 943  
 944  
 945  /**
 946   * Memorize that a specific js helper will be required by the current page.
 947   * This allows to require JS + SS + do init.
 948   *
 949   * All requested helpers will be included in the page head only once (when headlines is called)
 950   * Requested helpers should add their required translation strings and any other settings
 951   *
 952   * @param string helper, name of the required helper
 953   */
 954  function require_js_helper( $helper = '', $relative_to = 'rsc_url' )
 955  {
 956      static $helpers;
 957  
 958      if( empty( $helpers ) || !in_array( $helper, $helpers ) )
 959      { // Helper not already added, add the helper:
 960  
 961          switch( $helper )
 962          {
 963              case 'helper' :
 964                  // main helper object required
 965                  global $debug;
 966                  require_js( '#jquery#', $relative_to ); // dependency
 967                  require_js( 'helper.js', $relative_to );
 968                  add_js_headline('jQuery(document).ready(function()
 969                  {
 970                      b2evoHelper.Init({
 971                          debug:'.( $debug ? 'true' : 'false' ).'
 972                      });
 973                  });');
 974                  break;
 975  
 976              case 'communications' :
 977                  // communications object required
 978                  require_js_helper('helper', $relative_to ); // dependency
 979  
 980                  global $dispatcher;
 981                  require_js( 'communication.js', $relative_to );
 982                  add_js_headline('jQuery(document).ready(function()
 983                  {
 984                      b2evoCommunications.Init({
 985                          dispatcher:"'.$dispatcher.'"
 986                      });
 987                  });' );
 988                  // add translation strings
 989                  T_('Update cancelled', NULL, array( 'for_helper' => true ) );
 990                  T_('Update paused', NULL, array( 'for_helper' => true ) );
 991                  T_('Changes pending', NULL, array( 'for_helper' => true ) );
 992                  T_('Saving changes', NULL, array( 'for_helper' => true ) );
 993                  break;
 994  
 995              case 'colorbox':
 996                  // Colorbox: a lightweight Lightbox alternative -- allows zooming on images and slideshows in groups of images
 997                  // Added by fplanque - (MIT License) - http://colorpowered.com/colorbox/
 998                  require_js( '#jqueryUI#', $relative_to );
 999                  require_js( 'voting.js', $relative_to );
1000                  require_js( 'colorbox/jquery.colorbox-min.js', $relative_to );
1001                  require_css( 'colorbox/colorbox.css', $relative_to );
1002                  if( is_logged_in() )
1003                  {    // If user is logged in - display a voting panel
1004                      $colorbox_params = ',
1005                                  displayVoting: true,
1006                                  votingUrl: "'.get_secure_htsrv_url().'anon_async.php?action=voting&vote_type=file&'.url_crumb( 'voting' ).'",
1007                                  minWidth: 345';
1008                  }
1009                  else
1010                  {    // Set minimum width
1011                      $colorbox_params = ',
1012                                  minWidth: 255';
1013                  }
1014                  add_js_headline('jQuery(document).ready(function()
1015                          {
1016                              jQuery("a[rel^=\'lightbox\']").colorbox(
1017                              {
1018                                  maxWidth: "95%",
1019                                  maxHeight: "90%",
1020                                  slideshow: true,
1021                                  slideshowAuto: false'.
1022                                  $colorbox_params.'
1023                              } );
1024                          } );' );
1025                  // TODO: translation strings
1026                  break;
1027          }
1028          // add to list of loaded helpers
1029          $helpers[] = $helper;
1030      }
1031  }
1032  
1033  /**
1034   * Memorize that a specific translation will be required by the current page.
1035   * All requested translations will be included in the page body only once (when footerlines is called)
1036   *
1037   * @param string string, untranslated string
1038   * @param string translation, translated string
1039   */
1040  function add_js_translation( $string, $translation )
1041  {
1042      global $js_translations;
1043      if( $string != $translation )
1044      { // it's translated
1045          $js_translations[ $string ] = $translation;
1046      }
1047  }
1048  
1049  
1050  /**
1051   * Add a headline, which then gets output in the HTML HEAD section.
1052   * If you want to include CSS or JavaScript files, please use
1053   * {@link require_css()} and {@link require_js()} instead.
1054   * This avoids duplicates and allows caching/concatenating those files
1055   * later (not implemented yet)
1056   * @param string
1057   */
1058  function add_headline($headline)
1059  {
1060      global $headlines;
1061      $headlines[] = $headline;
1062  }
1063  
1064  
1065  /**
1066   * Add a Javascript headline.
1067   * This is an extra function, to provide consistent wrapping and allow to bundle it
1068   * (i.e. create a bundle with all required JS files and these inline code snippets,
1069   *  in the correct order).
1070   * @param string Javascript
1071   */
1072  function add_js_headline($headline)
1073  {
1074      add_headline("<script type=\"text/javascript\">\n\t/* <![CDATA[ */\n\t\t"
1075          .$headline."\n\t/* ]]> */\n\t</script>");
1076  }
1077  
1078  
1079  /**
1080   * Add a CSS headline.
1081   * This is an extra function, to provide consistent wrapping and allow to bundle it
1082   * (i.e. create a bundle with all required JS files and these inline code snippets,
1083   *  in the correct order).
1084   * @param string CSS
1085   */
1086  function add_css_headline($headline)
1087  {
1088      add_headline("<style type=\"text/css\">\n\t".$headline."\n\t</style>");
1089  }
1090  
1091  
1092  /**
1093   * Registers all the javascripts needed by the toolbar menu
1094   *
1095   * @todo fp> include basic.css ? -- rename to add_headlines_for* -- potential problem with inclusion order of CSS files!!
1096   *       dh> would be nice to have the batch of CSS in a separate file. basic.css would get included first always, then e.g. this toolbar.css.
1097   */
1098  function add_js_for_toolbar( $relative_to = 'rsc_url' )
1099  {
1100      if( ! is_logged_in() )
1101      { // the toolbar (blogs/skins/_toolbar.inc.php) gets only used when logged in.
1102          return false;
1103      }
1104  
1105      require_js( '#jquery#', $relative_to );
1106      // Superfish menus:
1107      require_js( 'hoverintent.js', $relative_to );
1108      require_js( 'superfish.js', $relative_to );
1109      add_js_headline( '
1110      jQuery( function() {
1111        jQuery("ul.sf-menu").superfish( {
1112          delay: 500, // mouseout
1113          animation: {opacity:"show",height:"show"},
1114          speed: "fast"
1115        } );
1116      } );');
1117  
1118      return true;
1119  }
1120  
1121  
1122  /**
1123   * Registers headlines required by AJAX forms, but only if javascript forms are enabled in blog settings.
1124   */
1125  function init_ajax_forms( $relative_to = 'blog' )
1126  {
1127      global $Blog;
1128  
1129      if( !empty($Blog) && $Blog->get_setting('ajax_form_enabled') )
1130      {
1131          require_js( 'communication.js', $relative_to );
1132      }
1133  }
1134  
1135  
1136  /**
1137   * Registers headlines required by comments forms
1138   */
1139  function init_ratings_js( $relative_to = 'blog', $force_init = false )
1140  {
1141      global $Item;
1142  
1143      // fp> Note, the following test is good for $disp == 'single', not for 'posts'
1144      if( $force_init || ( !empty($Item) && $Item->can_rate() ) )
1145      {
1146          require_js( '#jquery#', $relative_to ); // dependency
1147          require_js( 'jquery/jquery.raty.min.js', $relative_to );
1148      }
1149  }
1150  
1151  
1152  /**
1153   * Registers headlines required to a bubbletip above user login.
1154   */
1155  function init_bubbletip_js( $relative_to = 'rsc_url' )
1156  {
1157      if( ! check_setting( 'bubbletip' ) )
1158      { // If setting "bubbletip" is OFF for current case
1159          return;
1160      }
1161  
1162      require_js( '#jquery#', $relative_to ); // dependency
1163      require_js( 'jquery/jquery.bubbletip.min.js', $relative_to );
1164      require_js( 'bubbletip.js', $relative_to );
1165      require_css( 'jquery/bubbletip/bubbletip.css', $relative_to );
1166      add_headline('<!--[if IE]>');
1167      require_css( 'jquery/bubbletip/bubbletip-IE.css', $relative_to );
1168      add_headline('<![endif]-->');
1169  }
1170  
1171  
1172  /**
1173   * Registers headlines required to display a bubbletip to the right of user multi-field.
1174   */
1175  function init_userfields_js( $relative_to = 'rsc_url' )
1176  {
1177      require_js( '#jquery#', $relative_to ); // dependency
1178      require_js( 'jquery/jquery.bubbletip.min.js', $relative_to );
1179      require_js( 'userfields.js', $relative_to );
1180      require_css( 'jquery/bubbletip/bubbletip.css', $relative_to );
1181      add_headline('<!--[if IE]>');
1182      require_css( 'jquery/bubbletip/bubbletip-IE.css', $relative_to );
1183      add_headline('<![endif]-->');
1184  }
1185  
1186  
1187  /**
1188   * Registers headlines required to display a bubbletip to the right of plugin help icon.
1189   */
1190  function init_plugins_js( $relative_to = 'rsc_url' )
1191  {
1192      require_js( '#jquery#', $relative_to ); // dependency
1193      require_js( 'jquery/jquery.bubbletip.min.js', $relative_to );
1194      require_js( 'plugins.js', $relative_to );
1195      require_css( 'jquery/bubbletip/bubbletip.css', $relative_to );
1196      add_headline('<!--[if IE]>');
1197      require_css( 'jquery/bubbletip/bubbletip-IE.css', $relative_to );
1198      add_headline('<![endif]-->');
1199  }
1200  
1201  
1202  /**
1203   * Registers headlines for initialization of datepicker inputs
1204   */
1205  function init_datepicker_js( $relative_to = 'rsc_url' )
1206  {
1207      require_js( '#jquery#', $relative_to );
1208      require_js( '#jqueryUI#', $relative_to );
1209  
1210      $datefmt = locale_datefmt();
1211      $datefmt = str_replace( array( 'd', 'j', 'm', 'Y' ), array( 'dd', 'd', 'mm', 'yy' ), $datefmt );
1212      require_css( 'jquery/smoothness/jquery-ui.css' );
1213      add_js_headline( 'jQuery(document).ready( function(){
1214          var monthNames = ["'.T_('January').'","'.T_('February').'", "'.T_('March').'",
1215                            "'.T_('April').'", "'.T_('May').'", "'.T_('June').'",
1216                            "'.T_('July').'", "'.T_('August').'", "'.T_('September').'",
1217                            "'.T_('October').'", "'.T_('November').'", "'.T_('December').'"];
1218  
1219          var dayNamesMin = ["'.T_('Sun').'", "'.T_('Mon').'", "'.T_('Tue').'",
1220                            "'.T_('Wed').'", "'.T_('Thu').'", "'.T_('Fri').'", "'.T_('Sat').'"];
1221  
1222          var docHead = document.getElementsByTagName("head")[0];
1223          for (i=0;i<dayNamesMin.length;i++)
1224              dayNamesMin[i] = dayNamesMin[i].substr(0, 2)
1225  
1226          jQuery(".form_date_input").datepicker({
1227              dateFormat: "'.$datefmt.'",
1228              monthNames: monthNames,
1229              dayNamesMin: dayNamesMin,
1230              firstDay: '.locale_startofweek().'
1231          })
1232      })' );
1233  }
1234  
1235  
1236  /**
1237   * Registers headlines for initialization of scroll wide
1238   */
1239  function init_scrollwide_js( $relative_to = 'rsc_url' )
1240  {
1241      require_js( '#jquery#', $relative_to ); // dependency
1242      require_js( 'jquery/jquery.scrollwide.min.js', $relative_to );
1243      add_js_headline( 'jQuery( document ).ready( function()
1244          {
1245              jQuery( "div.wide_scroll" ).scrollWide( { scroll_time: 100 } );
1246          } )' );
1247      // require_css( 'jquery/scrollwide/jquery.scrollwide.css', $relative_to );
1248  }
1249  
1250  
1251  /**
1252   * Registers headlines for initialization of jQuery Tokeninput plugin
1253   */
1254  function init_tokeninput_js( $relative_to = 'rsc_url' )
1255  {
1256      require_js( '#jquery#', $relative_to ); // dependency
1257      require_js( 'jquery/jquery.tokeninput.js', $relative_to );
1258      require_css( 'jquery/jquery.token-input-facebook.css', $relative_to );
1259  }
1260  
1261  
1262  /**
1263   * Registers headlines for initialization of functions to work with Results tables
1264   */
1265  function init_results_js( $relative_to = 'rsc_url' )
1266  {
1267      require_js( '#jquery#', $relative_to ); // dependency
1268      require_js( 'results.js', $relative_to );
1269  }
1270  
1271  
1272  /**
1273   * Registers headlines for initialization of functions to work with Results tables
1274   */
1275  function init_voting_comment_js( $relative_to = 'rsc_url' )
1276  {
1277      global $Blog;
1278  
1279      if( empty( $Blog ) || ! is_logged_in( false ) || ! $Blog->get_setting('allow_rating_comment_helpfulness') )
1280      {    // If User is not logged OR Users cannot vote
1281          return false;
1282      }
1283  
1284      require_js( '#jquery#', $relative_to ); // dependency
1285      require_js( 'voting.js', $relative_to );
1286      add_js_headline( '
1287      jQuery( document ).ready( function()
1288      {
1289          var comment_voting_url = "'.get_secure_htsrv_url().'anon_async.php?action=voting&vote_type=comment&'.url_crumb( 'voting' ).'";
1290          jQuery( "span[id^=vote_helpful_]" ).each( function()
1291          {
1292              init_voting_bar( jQuery( this ), comment_voting_url, jQuery( this ).find( "#votingID" ).val(), false );
1293          } );
1294      } );
1295      ' );
1296  }
1297  
1298  
1299  /**
1300   * Registers headlines for initialization of colorpicker inputs
1301   */
1302  function init_colorpicker_js( $relative_to = 'rsc_url' )
1303  {
1304      require_js( '#jquery#', $relative_to );
1305      require_js( 'jquery/jquery.farbtastic.min.js', $relative_to );
1306      require_css( 'jquery/farbtastic/farbtastic.css' );
1307      add_js_headline( 'jQuery(document).ready( function() {
1308          jQuery( "body" ).append( "<div id=\"colorpicker\"></div>" );
1309          var farbtastic_colorpicker = jQuery.farbtastic( "#colorpicker" );
1310          jQuery( ".form_color_input" )
1311              .each( function () { farbtastic_colorpicker.linkTo( this ); } )
1312              .blur( function () { jQuery( "#colorpicker" ).hide(); } )
1313              .focus( function () {
1314                  farbtastic_colorpicker.linkTo( this );
1315                  jQuery( "#colorpicker" ).css( {
1316                      top: jQuery( this ).offset().top - 90,
1317                      left: jQuery( this ).offset().left + jQuery( this ).width() + 15,
1318                  } ).show();
1319              } );
1320      } );' );
1321  }
1322  
1323  
1324  /**
1325   * Registers headlines required to autocomplete the user logins
1326   *
1327   * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
1328   */
1329  function init_autocomplete_login_js( $relative_to = 'rsc_url' )
1330  {
1331      require_js( '#jquery#', $relative_to ); // dependency
1332  
1333      // Use hintbox plugin of jQuery
1334  
1335      // Add jQuery hintbox (autocompletion).
1336      // Form 'username' field requires the following JS and CSS.
1337      // fp> TODO: think about a way to bundle this with other JS on the page -- maybe always load hintbox in the backoffice
1338      //     dh> Handle it via http://www.appelsiini.net/projects/lazyload ?
1339      // dh> TODO: should probably also get ported to use jquery.ui.autocomplete (or its successor)
1340      require_css( 'jquery/jquery.hintbox.css', $relative_to );
1341      require_js( 'jquery/jquery.hintbox.min.js', $relative_to );
1342      add_js_headline( 'jQuery( document ).ready( function()
1343      {
1344          jQuery( "input.autocomplete_login" ).hintbox(
1345          {
1346              url: "'.get_secure_htsrv_url().'async.php?action=get_login_list",
1347              matchHint: true,
1348              autoDimentions: true
1349          } );
1350      } );' );
1351  }
1352  
1353  
1354  /**
1355   * Outputs the collected HTML HEAD lines.
1356   * @see add_headline()
1357   * @return string
1358   */
1359  function include_headlines()
1360  {
1361      global $headlines;
1362  
1363      if( $headlines )
1364      {
1365          echo "\n\t<!-- headlines: -->\n\t".implode( "\n\t", $headlines );
1366          echo "\n\n";
1367      }
1368  }
1369  
1370  
1371  /**
1372   * Outputs the collected translation lines before </body>
1373   *
1374   * yabs > Should this be expanded to similar functionality to headlines?
1375   *
1376   * @see add_js_translation()
1377   */
1378  function include_footerlines()
1379  {
1380      global $js_translations;
1381      if( empty( $js_translations ) )
1382      { // nothing to do
1383          return;
1384      }
1385      $r = '';
1386  
1387      foreach( $js_translations as $string => $translation )
1388      { // output each translation
1389          if( $string != $translation )
1390          { // this is translated
1391              $r .= '<div><span class="b2evo_t_string">'.$string.'</span><span class="b2evo_translation">'.$translation.'</span></div>'."\n";
1392          }
1393      }
1394      if( $r )
1395      { // we have some translations
1396          echo '<div id="b2evo_translations" style="display:none;">'."\n";
1397          echo $r;
1398          echo '</div>'."\n";
1399      }
1400  }
1401  
1402  
1403  /**
1404   * Template tag.
1405   */
1406  function app_version()
1407  {
1408      global $app_version;
1409      echo $app_version;
1410  }
1411  
1412  
1413  /**
1414   * Displays an empty or a full bullet based on boolean
1415   *
1416   * @param boolean true for full bullet, false for empty bullet
1417   */
1418  function bullet( $bool )
1419  {
1420      if( $bool )
1421          return get_icon( 'bullet_full', 'imgtag' );
1422  
1423      return get_icon( 'bullet_empty', 'imgtag' );
1424  }
1425  
1426  
1427  
1428  
1429  /**
1430   * Stub: Links to previous and next post in single post mode
1431   */
1432  function item_prevnext_links( $params = array() )
1433  {
1434      global $MainList;
1435  
1436      $params = array_merge( array( 'target_blog' => 'auto' ), $params );
1437  
1438      if( isset($MainList) )
1439      {
1440          $MainList->prevnext_item_links( $params );
1441      }
1442  }
1443  
1444  /**
1445   * Stub: Links to previous and next user in single user mode
1446   */
1447  function user_prevnext_links( $params = array() )
1448  {
1449      global $UserList;
1450  
1451      if( isset($UserList) )
1452      {
1453          $UserList->prevnext_user_links( $params );
1454      }
1455  }
1456  
1457  
1458  /**
1459   * Stub
1460   */
1461  function messages( $params = array() )
1462  {
1463      global $Messages;
1464  
1465      if( isset( $params['has_errors'] ) )
1466      {
1467          $params['has_errors'] = $Messages->has_errors();
1468      }
1469      $Messages->disp( $params['block_start'], $params['block_end'] );
1470  }
1471  
1472  
1473  /**
1474   * Stub: Links to list pages:
1475   */
1476  function mainlist_page_links( $params = array() )
1477  {
1478      global $MainList;
1479  
1480      if( isset($MainList) )
1481      {
1482          $MainList->page_links( $params );
1483      }
1484  }
1485  
1486  
1487  /**
1488   * Stub
1489   *
1490   * Sets $Item ion global scope
1491   *
1492   * @return Item
1493   */
1494  function & mainlist_get_item()
1495  {
1496      global $MainList, $featured_displayed_item_IDs;
1497  
1498      if( isset($MainList) )
1499      {
1500          $Item = & $MainList->get_item();
1501  
1502          if( $Item && in_array( $Item->ID, $featured_displayed_item_IDs ) )
1503          {    // This post was already displayed as a Featured post, let's skip it and get the next one:
1504              $Item = & $MainList->get_item();
1505          }
1506      }
1507      else
1508      {
1509          $Item = NULL;
1510      }
1511  
1512      $GLOBALS['Item'] = & $Item;
1513  
1514      return $Item;
1515  }
1516  
1517  
1518  /**
1519   * Stub
1520   *
1521   * @return boolean true if empty MainList
1522   */
1523  function display_if_empty( $params = array() )
1524  {
1525      global $MainList, $featured_displayed_item_IDs;
1526  
1527      if( isset( $MainList ) && empty( $featured_displayed_item_IDs ) )
1528      {
1529          return $MainList->display_if_empty( $params );
1530      }
1531  
1532      return NULL;
1533  }
1534  
1535  
1536  /**
1537   * Template tag for credits
1538   *
1539   * Note: You can limit (and even disable) the number of links being displayed here though the Admin interface:
1540   * Blog Settings > Advanced > Software credits
1541   *
1542   * @param array
1543   */
1544  function credits( $params = array() )
1545  {
1546      /**
1547       * @var AbstractSettings
1548       */
1549      global $global_Cache;
1550      global $Blog;
1551  
1552      // Make sure we are not missing any param:
1553      $params = array_merge( array(
1554              'list_start'  => ' ',
1555              'list_end'    => ' ',
1556              'item_start'  => ' ',
1557              'item_end'    => ' ',
1558              'separator'   => ',',
1559              'after_item'  => '#',
1560          ), $params );
1561  
1562  
1563      $cred_links = $global_Cache->get( 'creds' );
1564      if( empty( $cred_links ) )
1565      {    // Use basic default:
1566          $cred_links = unserialize('a:2:{i:0;a:2:{i:0;s:24:"http://b2evolution.net/r";i:1;s:18:"free blog software";}i:1;a:2:{i:0;s:36:"http://b2evolution.net/web-hosting/r";i:1;s:19:"quality web hosting";}}');
1567      }
1568  
1569      $max_credits = (empty($Blog) ? NULL : $Blog->get_setting( 'max_footer_credits' ));
1570  
1571      display_list( $cred_links, $params['list_start'], $params['list_end'], $params['separator'], $params['item_start'], $params['item_end'], NULL, $max_credits );
1572  }
1573  
1574  
1575  /**
1576   * Get rating as 5 stars
1577   *
1578   * @param integer Number of stars
1579   * @param string Class name
1580   * @return string Template for star rating
1581   */
1582  function get_star_rating( $stars, $class = 'not-used-any-more' )
1583  {
1584      if( is_null( $stars ) )
1585      {
1586          return;
1587      }
1588  
1589      $average = ceil( ( $stars ) / 5 * 100 );
1590  
1591      return '<div class="star_rating"><div style="width:'.$average.'%">'.$stars.' stars</div></div>';
1592  }
1593  
1594  
1595  /**
1596   * Display rating as 5 stars
1597   *
1598   * @param integer Number of stars
1599   * @param string Class name
1600   */
1601  function star_rating( $stars, $class = 'not-used-any-more' )
1602  {
1603      echo get_star_rating( $stars, $class );
1604  }
1605  
1606  
1607  /**
1608   * Display "powered by b2evolution" logo
1609   */
1610  function powered_by( $params = array() )
1611  {
1612      /**
1613       * @var AbstractSettings
1614       */
1615      global $global_Cache;
1616  
1617      global $rsc_uri;
1618  
1619      // Make sure we are not missing any param:
1620      $params = array_merge( array(
1621              'block_start' => '<div class="powered_by">',
1622              'block_end'   => '</div>',
1623              'img_url'     => '$rsc$img/powered-by-b2evolution-120t.gif',
1624              'img_width'   => '',
1625              'img_height'  => '',
1626          ), $params );
1627  
1628      echo $params['block_start'];
1629  
1630      $img_url = str_replace( '$rsc$', $rsc_uri, $params['img_url'] );
1631  
1632      $evo_links = $global_Cache->get( 'evo_links' );
1633      if( empty( $evo_links ) )
1634      {    // Use basic default:
1635          $evo_links = unserialize('a:1:{s:0:"";a:1:{i:0;a:3:{i:0;i:100;i:1;s:23:"http://b2evolution.net/";i:2;a:2:{i:0;a:2:{i:0;i:55;i:1;s:36:"powered by b2evolution blog software";}i:1;a:2:{i:0;i:100;i:1;s:29:"powered by free blog software";}}}}}');
1636      }
1637  
1638      echo resolve_link_params( $evo_links, NULL, array(
1639              'type'        => 'img',
1640              'img_url'     => $img_url,
1641              'img_width'   => $params['img_width'],
1642              'img_height'  => $params['img_height'],
1643              'title'       => 'b2evolution: next generation blog software',
1644          ) );
1645  
1646      echo $params['block_end'];
1647  }
1648  
1649  
1650  
1651  /**
1652   * DEPRECATED
1653   */
1654  function bloginfo( $what )
1655  {
1656      global $Blog;
1657      $Blog->disp( $what );
1658  }
1659  
1660  /**
1661   * Display allowed tags for comments
1662   * (Mainly provided for WP compatibility. Not recommended for use)
1663   *
1664   * @param string format
1665   */
1666  function comment_allowed_tags( $format = 'htmlbody' )
1667  {
1668      global $comment_allowed_tags;
1669  
1670      echo format_to_output( $comment_allowed_tags, $format );
1671  }
1672  
1673  /**
1674   * DEPRECATED
1675   */
1676  function link_pages()
1677  {
1678      echo '<!-- link_pages() is DEPRECATED -->';
1679  }
1680  
1681  
1682  /**
1683   * Return a formatted percentage (should probably go to _misc.funcs)
1684   */
1685  function percentage( $hit_count, $hit_total, $decimals = 1, $dec_point = '.' )
1686  {
1687      $percentage = $hit_total > 0 ? $hit_count * 100 / $hit_total : 0;
1688      return number_format( $percentage, $decimals, $dec_point, '' ).'&nbsp;%';
1689  }
1690  
1691  function addup_percentage( $hit_count, $hit_total, $decimals = 1, $dec_point = '.' )
1692  {
1693      static $addup = 0;
1694  
1695      $addup += $hit_count;
1696      return number_format( $addup * 100 / $hit_total, $decimals, $dec_point, '' ).'&nbsp;%';
1697  }
1698  
1699  
1700  /**
1701   * Check if the array given as the first param contains recursion
1702   *
1703   * @param array what to check
1704   * @param array contains object which were already seen
1705   * @return boolean true if contains recursion false otherwise
1706   */
1707  function is_recursive( /*array*/ & $array, /*array*/ & $alreadySeen = array() )
1708  {
1709      static $uniqueObject;
1710      if( !$uniqueObject )
1711      {
1712          $uniqueObject = new stdClass;
1713      }
1714  
1715      // Set main array as already seen
1716      $alreadySeen[] = & $array;
1717  
1718      foreach( $array as & $item )
1719      { // for each item in array
1720          if( !is_array( $item ) )
1721          { // if not array, we don't have to check it
1722              continue;
1723          }
1724  
1725          // put the unique object into the end of the array
1726          $item[] = $uniqueObject;
1727          $recursionDetected = false;
1728          foreach( $alreadySeen as $candidate )
1729          {
1730              if( end( $candidate ) === $uniqueObject )
1731              { // In the end of an already scanned array is the same unique Obect, this means that recursion was detected
1732                  $recursionDetected = true;
1733                  break;
1734              }
1735          }
1736  
1737          array_pop( $item );
1738  
1739          if( $recursionDetected || is_recursive( $item, $alreadySeen ) )
1740          { // Check until recursion detected or there are not more arrays
1741              return true;
1742          }
1743      }
1744  
1745      return false;
1746  }
1747  
1748  
1749  /**
1750   * Display a form (like comment or contact form) through an ajax call
1751   *
1752   * @param array params
1753   */
1754  function display_ajax_form( $params )
1755  {
1756      global $rsc_uri, $samedomain_htsrv_url, $ajax_form_number;
1757  
1758      if( is_recursive( $params ) )
1759      { // The params array contains recursion, don't try to encode, display error message instead
1760          // We don't use translation because this situation should not really happen ( Probably it happesn with some wrong skin )
1761          echo '<p style="color:red;font-weight:bold">'.T_( 'This section can\'t be displayed because wrong params were created by the skin.' ).'</p>';
1762          return;
1763      }
1764  
1765      if( empty( $ajax_form_number ) )
1766      {    // Set number for ajax form to use unique ID for each new form
1767          $ajax_form_number = 0;
1768      }
1769      $ajax_form_number++;
1770  
1771      echo '<div id="ajax_form_number_'.$ajax_form_number.'" class="section_requires_javascript">';
1772  
1773      // Needs json_encode function to create json type params
1774      $json_params = evo_json_encode( $params );
1775      $ajax_loader = "<p class='ajax-loader'><img src='".$rsc_uri."img/ajax-loader2.gif' /><br />".T_( 'Form is loading...' )."</p>";
1776      ?>
1777      <script type="text/javascript">
1778          // display loader gif until the ajax call returns
1779          document.write( <?php echo '"'.$ajax_loader.'"'; ?> );
1780  
1781          var ajax_form_offset_<?php echo $ajax_form_number; ?> = jQuery('#ajax_form_number_<?php echo $ajax_form_number; ?>').offset().top;
1782          var request_sent_<?php echo $ajax_form_number; ?> = false;
1783  
1784  		function get_form_<?php echo $ajax_form_number; ?>()
1785          {
1786              jQuery.ajax({
1787                  url: '<?php echo $samedomain_htsrv_url; ?>anon_async.php',
1788                  type: 'POST',
1789                  data: <?php echo $json_params; ?>,
1790                  success: function(result)
1791                      {
1792                          jQuery('#ajax_form_number_<?php echo $ajax_form_number; ?>').html( ajax_debug_clear( result ) );
1793                      }
1794              });
1795          }
1796  
1797  		function check_and_show_<?php echo $ajax_form_number; ?>()
1798          {
1799              var window_scrollTop = jQuery(window).scrollTop();
1800              var window_height = jQuery(window).height();
1801              // check if the ajax form is visible, or if it will be visible soon ( 20 pixel )
1802              if( window_scrollTop >= ajax_form_offset_<?php echo $ajax_form_number; ?> - window_height - 20 )
1803              {
1804                  if( !request_sent_<?php echo $ajax_form_number; ?> )
1805                  {
1806                      request_sent_<?php echo $ajax_form_number; ?> = true;
1807                      // get the form
1808                      get_form_<?php echo $ajax_form_number; ?>();
1809                  }
1810              }
1811          }
1812  
1813          jQuery(window).scroll(function() {
1814              check_and_show_<?php echo $ajax_form_number; ?>();
1815          });
1816  
1817          jQuery(document).ready( function() {
1818              check_and_show_<?php echo $ajax_form_number; ?>();
1819          });
1820  
1821          jQuery(window).resize( function() {
1822              check_and_show_<?php echo $ajax_form_number; ?>();
1823          });
1824      </script>
1825      <noscript>
1826          <?php echo '<p>'.T_( 'This section can only be displayed by javascript enabled browsers.' ).'</p>'; ?>
1827      </noscript>
1828      <?php
1829      echo '</div>';
1830  }
1831  
1832  
1833  /**
1834   * Display login form
1835   *
1836   * @param array params
1837   */
1838  function display_login_form( $params )
1839  {
1840      global $Settings, $Plugins, $Session, $Blog, $blog, $dummy_fields;
1841      global $secure_htsrv_url, $admin_url, $baseurl, $ReqHost;
1842  
1843      $params = array_merge( array(
1844              'form_action' => '',
1845              'form_name' => 'login_form' ,
1846              'form_layout' => '',
1847              'form_class' => 'bComment',
1848              'source' => 'inskin login form',
1849              'inskin' => true,
1850              'login_required' => true,
1851              'validate_required' => NULL,
1852              'redirect_to' => '',
1853              'login' => '',
1854              'action' => '',
1855              'reqID' => '',
1856              'sessID' => '',
1857              'transmit_hashed_password' => false,
1858          ), $params );
1859  
1860      $inskin = $params[ 'inskin' ];
1861      $login = $params[ 'login' ];
1862      $redirect_to = $params[ 'redirect_to' ];
1863      $links = array();
1864  
1865      if( empty( $params[ 'login_required' ] )
1866          && $params[ 'action' ] != 'req_validatemail'
1867          && strpos($redirect_to, $admin_url) !== 0
1868          && strpos($ReqHost.$redirect_to, $admin_url ) !== 0 )
1869      { // No login required, allow to pass through
1870          // TODO: dh> validate redirect_to param?!
1871          // check if redirect_to url requires logged in user
1872          if( require_login( $redirect_to, true ) )
1873          { // logged in user require for redirect_to url
1874              if( !empty( $blog ) )
1875              { // blog is set
1876                  if( empty( $Blog ) )
1877                  {
1878                      $BlogCache = & get_BlogCache();
1879                      $Blog = $BlogCache->get_by_ID( $blog, false );
1880                  }
1881                  // set abort url to Blog url
1882                  $abort_url = $Blog->gen_blogurl();
1883              }
1884              else
1885              { // set abort login url to base url
1886                  $abort_url = $baseurl;
1887              }
1888          }
1889          else
1890          { // logged in user isn't required for redirect_to url, set abort url to redirect_to
1891              $abort_url = $redirect_to;
1892          }
1893          $links[] = '<a href="'.htmlspecialchars( url_rel_to_same_host( $abort_url, $ReqHost ) ).'">'
1894          ./* Gets displayed as link to the location on the login form if no login is required */ T_('Abort login!').'</a>';
1895      }
1896  
1897      if( ( !$inskin ) && is_logged_in() )
1898      { // if we arrive here, but are logged in, provide an option to logout (e.g. during the email validation procedure)
1899          $links[] = get_user_logout_link();
1900      }
1901  
1902      if( count($links) )
1903      {
1904          echo '<div style="float:right; margin: 0 1em">'.implode( $links, ' &middot; ' ).'</div>
1905          <div class="clear"></div>';
1906      }
1907  
1908      $Form = new Form( $params[ 'form_action' ] , $params[ 'form_name' ], 'post', $params[ 'form_layout' ] );
1909  
1910      $Form->begin_form( $params[ 'form_class' ] );
1911  
1912      $Form->add_crumb( 'loginform' );
1913      $source = param( 'source', 'string', $params[ 'source' ].' login form' );
1914      $Form->hidden( 'source', $source );
1915      $Form->hidden( 'redirect_to', $redirect_to );
1916      if( $inskin )
1917      { // inskin login form
1918          $Form->hidden( 'inskin', true );
1919          $separator = '<br />';
1920      }
1921      else
1922      { // standard login form
1923          $Form->hidden( 'validate_required', $params[ 'validate_required' ] );
1924          if( isset( $params[ 'action' ],  $params[ 'reqID' ], $params[ 'sessID' ] ) &&  $params[ 'action' ] == 'validatemail' )
1925          { // the user clicked the link from the "validate your account" email, but has not been logged in; pass on the relevant data:
1926              $Form->hidden( 'action', 'validatemail' );
1927              $Form->hidden( 'reqID', $params[ 'reqID' ] );
1928              $Form->hidden( 'sessID', $params[ 'sessID' ] );
1929          }
1930          $separator = '';
1931      }
1932  
1933      // check if should transmit hashed password
1934      if( $params[ 'transmit_hashed_password' ] )
1935      { // used by JS-password encryption/hashing:
1936          $pwd_salt = $Session->get('core.pwd_salt');
1937          if( empty($pwd_salt) )
1938          { // Do not regenerate if already set because we want to reuse the previous salt on login screen reloads
1939              // fp> Question: the comment implies that the salt is reset even on failed login attemps. Why that? I would only have reset it on successful login. Do experts recommend it this way?
1940              // but if you kill the session you get a new salt anyway, so it's no big deal.
1941              // At that point, why not reset the salt at every reload? (it may be good to keep it, but I think the reason should be documented here)
1942              $pwd_salt = generate_random_key(64);
1943              $Session->set( 'core.pwd_salt', $pwd_salt, 86400 /* expire in 1 day */ );
1944              $Session->dbsave(); // save now, in case there's an error later, and not saving it would prevent the user from logging in.
1945          }
1946          $Form->hidden( 'pwd_salt', $pwd_salt );
1947          $Form->hidden( 'pwd_hashed', '' ); // gets filled by JS
1948      }
1949  
1950      $Form->begin_field();
1951      $Form->text_input( $dummy_fields[ 'login' ], $params[ 'login' ], 18, T_('Login'), $separator.T_('Enter your username (or email address).'),
1952                      array( 'maxlength' => 255, 'class' => 'input_text', 'required'=>true ) );
1953      $Form->end_field();
1954  
1955      if( $inskin )
1956      {
1957          $lost_password_url = regenerate_url( 'disp', 'disp=lostpassword' );
1958      }
1959      else
1960      {
1961          $lost_password_url = $secure_htsrv_url.'login.php?action=lostpassword&amp;redirect_to='.rawurlencode( url_rel_to_same_host( $redirect_to, $secure_htsrv_url) );
1962      }
1963      if( !empty($login) )
1964      {
1965          $lost_password_url .= '&amp;'.$dummy_fields[ 'login' ].'='.rawurlencode($login);
1966      }
1967      $pwd_note = $pwd_note = '<a href="'.$lost_password_url.'">'.T_('Lost password ?').'</a>';
1968  
1969      $Form->begin_field();
1970      $Form->password_input( $dummy_fields[ 'pwd' ], '', 18, T_('Password'), array( 'note'=>$pwd_note, 'maxlength' => 70, 'class' => 'input_text', 'required'=>true ) );
1971      $Form->end_field();
1972  
1973      // Allow a plugin to add fields/payload
1974      $Plugins->trigger_event( 'DisplayLoginFormFieldset', array( 'Form' => & $Form ) );
1975  
1976      // Submit button(s):
1977      $submit_buttons = array( array( 'name'=>'login_action[login]', 'value'=>T_('Log in!'), 'class'=>'search', 'style'=>'font-size: 120%' ) );
1978      if( ( !$inskin ) && ( strpos( $redirect_to, $admin_url ) !== 0 )
1979          && ( strpos( $ReqHost.$redirect_to, $admin_url ) !== 0 )// if $redirect_to is relative
1980          && ( ! is_admin_page() ) )
1981      { // provide button to log straight into backoffice, if we would not go there anyway
1982          $submit_buttons[] = array( 'name'=>'login_action[redirect_to_backoffice]', 'value'=>T_('Log into backoffice!'), 'class'=>'search' );
1983      }
1984  
1985      $Form->buttons_input( $submit_buttons );
1986  
1987      if( $inskin )
1988      {
1989          $before_register_link = '<strong>';
1990          $after_register_link = '</strong>';
1991          $register_link_style = 'text-align:right; margin: 1em 0 1ex';
1992      }
1993      else
1994      {
1995          echo '<div class="center notes" style="margin: 1em 0">'.T_('You will have to accept cookies in order to log in.').'</div>';
1996  
1997          // Passthrough REQUEST data (when login is required after having POSTed something)
1998          // (Exclusion of 'login_action', 'login', and 'action' has been removed. This should get handled via detection in Form (included_input_field_names),
1999          //  and "action" is protected via crumbs)
2000          $Form->hiddens_by_key( remove_magic_quotes($_REQUEST) );
2001  
2002          $before_register_link = '';
2003          $after_register_link = '';
2004          $register_link_style = 'text-align:right';
2005      }
2006  
2007      echo '<div class="login_actions" style="'.$register_link_style.'">';
2008      echo get_user_register_link( $before_register_link, $after_register_link, T_('No account yet? Register here').' &raquo;', '#', true /*disp_when_logged_in*/, $redirect_to, $source );
2009      echo '</div>';
2010  
2011      $Form->end_form();
2012  
2013      echo '<script type="text/javascript">';
2014      // Autoselect login text input or pwd input, if there\'s a login already:
2015      echo 'var login = document.getElementById("'.$dummy_fields[ 'login' ].'");
2016          if( login.value.length > 0 )
2017          {    // Focus on the password field:
2018              document.getElementById("'.$dummy_fields[ 'pwd' ].'").focus();
2019          }
2020          else
2021          {    // Focus on the login field:
2022              login.focus();
2023          }';
2024  
2025      if( $params[ 'transmit_hashed_password' ] )
2026      { // Hash the password onsubmit and clear the original pwd field
2027          // TODO: dh> it would be nice to disable the clicked/used submit button. That's how it has been when the submit was attached to the submit button(s)
2028          echo 'addEvent( document.getElementById("login_form"), "submit", function(){'.
2029                  /* this.value = '.TS_('Please wait...').' */
2030                  'var form = document.getElementById("login_form");'.
2031  
2032                  // Calculate hashed password and set it in the form:
2033                  'if( form.pwd_hashed && form.'.$dummy_fields[ 'pwd' ].' && form.pwd_salt && typeof hex_sha1 != "undefined" && typeof hex_md5 != "undefined" )
2034                  {'.
2035                      // We first hash to md5, because that's how the passwords are stored in the database
2036                      // We then hash with the salt using SHA1 (fp> can't we do that with md5 again, in order to load 1 less Javascript library?)
2037                      // NOTE: MD5 is kind of "weak" and therefor we also use SHA1
2038                      'form.pwd_hashed.value = hex_sha1( hex_md5(form.'.$dummy_fields[ 'pwd' ].'.value) + form.pwd_salt.value );
2039                      form.'.$dummy_fields[ 'pwd' ].'.value = "padding_padding_padding_padding_padding_padding_hashed_'.$Session->ID.'";'. /* to detect cookie problems */
2040                      // (paddings to make it look like encryption on screen. When the string changes to just one more or one less *, it looks like the browser is changing the password on the fly)
2041                  '}
2042                  return true;
2043              }, false );';
2044      }
2045      echo '</script>';
2046  }
2047  
2048  
2049  /**
2050   * Display lost password form
2051   *
2052   * @param array login form hidden params
2053   */
2054  function display_lostpassword_form( $login, $hidden_params )
2055  {
2056      global $secure_htsrv_url, $dummy_fields;
2057      $Form = new Form( $secure_htsrv_url.'login.php', '', 'post', 'fieldset' );
2058  
2059      $Form->begin_form( 'fform' );
2060  
2061      // Display hidden fields
2062      $Form->add_crumb( 'lostpassform' );
2063      $Form->hidden( 'action', 'retrievepassword' );
2064      foreach( $hidden_params as $key => $value )
2065      {
2066          $Form->hidden( $key, $value );
2067      }
2068  
2069      $Form->begin_fieldset();
2070  
2071      echo '<ol>';
2072      echo '<li>'.T_('Please enter your login (or email address) below.').'</li>';
2073      echo '<li>'.T_('An email will be sent to your registered email address immediately.').'</li>';
2074      echo '<li>'.T_('As soon as you receive the email, click on the link therein to change your password.').'</li>';
2075      echo '<li>'.T_('Your browser will open a page where you can chose a new password.').'</li>';
2076      echo '</ol>';
2077      echo '<p class="red"><strong>'.T_('Important: for security reasons, you must do steps 1 and 4 on the same computer and same web browser. Do not close your browser in between.').'</strong></p>';
2078  
2079      $Form->text( $dummy_fields[ 'login' ], $login, 30, T_('Login'), '', 255, 'input_text' );
2080  
2081      $Form->buttons_input( array(array( /* TRANS: Text for submit button to request an activation link by email */ 'value' => T_('Send me an email now!'), 'class' => 'ActionButton' )) );
2082  
2083      $Form->end_fieldset();;
2084  
2085      $Form->end_form();
2086  }
2087  
2088  
2089  /**
2090   * Display user activate info form content
2091   *
2092   * @param Object activateinfo Form
2093   */
2094  function display_activateinfo( $params )
2095  {
2096      global $current_User, $Settings, $UserSettings, $Plugins;
2097      global $secure_htsrv_url, $rsc_path, $rsc_url, $dummy_fields;
2098  
2099      if( !is_logged_in() )
2100      { // if this happens, it means the code is not correct somewhere before this
2101          debug_die( "You must log in to see this page." );
2102      }
2103  
2104      // init force request new email address param
2105      $force_request = param( 'force_request', 'boolean', false );
2106  
2107      // get last activation email timestamp from User Settings
2108      $last_activation_email_date = $UserSettings->get( 'last_activation_email', $current_User->ID );
2109  
2110      if( $force_request || empty( $last_activation_email_date ) )
2111      { // notification email was not sent yet, or user needs another one ( forced request )
2112          $params = array_merge( array(
2113                  'form_action' => $secure_htsrv_url.'login.php',
2114                  'form_name' => 'form_validatemail',
2115                  'form_class' => 'fform',
2116                  'form_layout' => 'fieldset',
2117                  'inskin' => false,
2118              ), $params );
2119          $Form = new Form( $params[ 'form_action' ], $params[ 'form_name' ], 'post', $params[ 'form_layout' ] );
2120  
2121          $Form->begin_form( $params[ 'form_class' ] );
2122  
2123          $Form->add_crumb( 'validateform' );
2124          $Form->hidden( 'action', 'req_validatemail');
2125          $Form->hidden( 'redirect_to', $params[ 'redirect_to' ] );
2126          if( $params[ 'inskin' ] )
2127          {
2128              $Form->hidden( 'inskin', $params[ 'inskin' ] );
2129              $Form->hidden( 'blog', $params[ 'blog' ] );
2130          }
2131          $Form->hidden( 'req_validatemail_submit', 1 ); // to know if the form has been submitted
2132  
2133          $Form->begin_fieldset();
2134  
2135          echo '<ol>';
2136          echo '<li>'.T_('Please confirm your email address below:').'</li>';
2137          echo '</ol>';
2138  
2139          // set email text input content only if this is not a forced request. This way the user may have bigger chance to write a correct email address.
2140          $user_email = ( $force_request ? '' : $current_User->email );
2141          // fp> note: 45 is the max length for evopress skin.
2142          $Form->text_input( $dummy_fields[ 'email' ], $user_email, 45, T_('Your email'), '', array( 'maxlength'=>255, 'class'=>'input_text', 'required'=>true ) );
2143          $Form->end_fieldset();
2144  
2145          // Submit button:
2146          $submit_button = array( array( 'name'=>'submit', 'value'=>T_('Send me a new activation email now!'), 'class'=>'submit' ) );
2147  
2148          $Form->buttons_input($submit_button);
2149  
2150          if( !$params[ 'inskin' ] )
2151          {
2152              $Plugins->trigger_event( 'DisplayValidateAccountFormFieldset', array( 'Form' => & $Form ) );
2153          }
2154  
2155          $Form->end_form();
2156  
2157          return;
2158      }
2159  
2160      // get notification email from general Settings
2161      $notification_email = $Settings->get( 'notification_sender_email' );
2162      // convert date to timestamp
2163      $last_activation_email_ts = mysql2timestamp( $last_activation_email_date );
2164      // get difference between local time and server time
2165      $time_difference = $Settings->get('time_difference');
2166      // get last activation email local date and time
2167      $last_email_date = date( locale_datefmt(), $last_activation_email_ts + $time_difference );
2168      $last_email_time = date( locale_shorttimefmt(), $last_activation_email_ts + $time_difference );
2169      $user_email = $current_User->email;
2170  
2171      echo '<ol start="1" class="expanded">';
2172      $instruction =  sprintf( T_('Open your email account for %s and find a message we sent you on %s at %s with the following title:'), $user_email, $last_email_date, $last_email_time );
2173      echo '<li>'.$instruction.'<br /><b>'.sprintf( T_('Activate your account: %s'), $current_User->login ).'</b>';
2174      $request_validation_url = 'href="'.regenerate_url( '', 'force_request=1&validate_required=true&redirect_to='.$params[ 'redirect_to' ] ).'"';
2175      echo '<p>'.sprintf( T_('NOTE: If you don\'t find it, check your "Junk", "Spam" or "Unsolicited email" folders. If you really can\'t find it, <a %s>request a new activation email</a>.'), $request_validation_url ).'</p></li>';
2176      echo '<li>'.sprintf( T_('Add us (%s) to your contacts to make sure you receive future email notifications, especially when someone sends you a private message.'), '<b><span class="nowrap">'.$notification_email.'</span></b>').'</li>';
2177      echo '<li><b class="red">'.T_('Click on the activation link in the email.').'</b>';
2178      echo '<p>'.T_('If this does not work, please copy/paste that link into the address bar of your browser.').'</p>';
2179      echo '<p>'.sprintf( T_('If you need assistance, please send an email to %s'), '<b><a href="mailto:"'.$notification_email.'"><span class="nowrap">'.$notification_email.'</span></a></b>' ).'</p></li>';
2180      echo '</ol>';
2181  
2182      if( (strpos( $user_email, '@hotmail.' ) || strpos( $user_email, '@live.' ) || strpos( $user_email, '@msn.' ))
2183          && file_exists( $rsc_path.'img/login_help/hotmail-validation.png' ) )
2184      {    // The user is on hotmail and we have a help screen to show him: (needs to be localized and include correct site name)
2185          echo '<div class="center" style="margin: 2em auto"><img src="'.$rsc_url.'img/login_help/hotmail-validation.png" /></div>';
2186      }
2187  }
2188  
2189  
2190  /*
2191   * Display javascript password strength indicator bar
2192   *
2193   * @param array Params
2194   */
2195  function display_password_indicator( $params = array() )
2196  {
2197      global $Blog, $rsc_url, $disp, $dummy_fields;
2198  
2199      $params = array_merge( array(
2200              'pass1-id'    => $dummy_fields[ 'pass1' ],
2201              'pass2-id'    => $dummy_fields[ 'pass2' ],
2202              'login-id'    => $dummy_fields[ 'login' ],
2203              'email-id'    => $dummy_fields[ 'email' ],
2204              'field-width' => 140,
2205              'disp-status' => 1,
2206              'disp-time'   => 0,
2207              'blacklist'   => "'b2evo','b2evolution'", // Identify the password as "weak" if it includes any of these words
2208          ), $params );
2209  
2210      $extra_bar_width = 2;
2211      $container_left_margin = 0;
2212      if( !empty($disp) )
2213      {    // In skin password form
2214          $extra_bar_width = 0;
2215          $container_left_margin = '3px';
2216      }
2217  
2218      echo "<script type='text/javascript'>
2219      // Load password strength estimation library
2220      (function(){var a;a=function(){var a,b;b=document.createElement('script');b.src='".$rsc_url."js/zxcvbn.js';b.type='text/javascript';b.async=!0;a=document.getElementsByTagName('script')[0];return a.parentNode.insertBefore(b,a)};null!=window.attachEvent?window.attachEvent('onload',a):window.addEventListener('load',a,!1)}).call(this);
2221  
2222      // Call 'passcheck' function when document is loaded
2223      if( document.addEventListener ) { document.addEventListener('DOMContentLoaded', passcheck, false); } else { window.attachEvent('onload', passcheckpasscheck); }
2224  
2225  	function passcheck()
2226      {
2227          var pass1input = document.getElementById('".$params['pass1-id']."');
2228          if( pass1input == null ) {
2229              return; // password field not found
2230          }
2231  
2232          var pass2input = document.getElementById('".$params['pass2-id']."');
2233          if( pass2input != null ) {
2234              pass2input.style.width = '".($params['field-width'] - 2)."px'; // Set fixed length
2235          }
2236  
2237          // Prepair password field
2238          pass1input.style.width = '".($params['field-width'] - 2)."px'; // Set fixed length
2239          pass1input.setAttribute('onkeyup','return passinfo(this);'); // Add onkeyup attribute
2240          pass1input.parentNode.innerHTML += \"<div id='p-container'><div id='p-result'></div><div id='p-status'></div><div id='p-time'></div></div>\";
2241  
2242          var pstyle = document.createElement('style');
2243          pstyle.innerHTML += '#p-container { position: relative; margin: 4px 0 0 ".$container_left_margin."; width:".($params['field-width']+$extra_bar_width)."px; height:5px; border: 1px solid #CCC; font-size: 84%; line-height:normal; color: #999 }';
2244          pstyle.innerHTML += '#p-result { height:5px }';
2245          pstyle.innerHTML += '#p-status { position:absolute; width: 100px; top:-7px; left:".($params['field-width']+8)."px }';
2246          pstyle.innerHTML += '#p-time { position:absolute; width: 400px }';
2247          document.body.appendChild(pstyle);
2248      }
2249  
2250  	function passinfo(el)
2251      {
2252          var presult = document.getElementById('p-result');
2253          var pstatus = document.getElementById('p-status');
2254          var ptime = document.getElementById('p-time');
2255  
2256          var vlogin = '';
2257          var login = document.getElementById('".$params['login-id']."');
2258          if( login != null && login.value != '' ) { vlogin = login.value; }
2259  
2260          var vemail = '';
2261          var email = document.getElementById('".$params['email-id']."');
2262          if( email != null && email.value != '' ) { vemail = email.value; }
2263  
2264          // Check the password
2265          var passcheck = zxcvbn(el.value, [vlogin, vemail, ".$params['blacklist']."]);
2266  
2267          var bar_color = 'red';
2268          var bar_status = '".format_to_output( T_('Very weak'), 'htmlattr' )."';
2269  
2270          if( el.value.length == 0 ) {
2271              presult.style.display = 'none';
2272              pstatus.style.display = 'none';
2273              ptime.style.display = 'none';
2274          } else {
2275              presult.style.display = 'block';
2276              pstatus.style.display = 'block';
2277              ptime.style.display = 'block';
2278          }
2279  
2280          switch(passcheck.score) {
2281              case 1:
2282                  bar_color = '#F88158';
2283                  bar_status = '".format_to_output( T_('Weak'), 'htmlattr' )."';
2284                  break;
2285              case 2:
2286                  bar_color = '#FBB917';
2287                  bar_status = '".format_to_output( T_('So-so'), 'htmlattr' )."';
2288                  break;
2289              case 3:
2290                  bar_color = '#8BB381';
2291                  bar_status = '".format_to_output( T_('Good'), 'htmlattr' )."';
2292                  break;
2293              case 4:
2294                  bar_color = '#59E817';
2295                  bar_status = '".format_to_output( T_('Great!'), 'htmlattr' )."';
2296                  break;
2297          }
2298  
2299          presult.style.width = (passcheck.score * 20 + 20)+'%';
2300          presult.style.background = bar_color;
2301  
2302          if( ".$params['disp-status']." ) {
2303              pstatus.innerHTML = bar_status;
2304          }
2305          if( ".$params['disp-time']." ) {
2306              document.getElementById('p-time').innerHTML = '".TS_('Estimated crack time').": ' + passcheck.crack_time_display;
2307          }
2308      }
2309  </script>";
2310  }
2311  
2312  
2313  /*
2314   * Display javascript login validator
2315   *
2316   * @param array Params
2317   */
2318  function display_login_validator( $params = array() )
2319  {
2320      global $rsc_url, $dummy_fields;
2321  
2322      $params = array_merge( array(
2323              'login-id'    => $dummy_fields[ 'login' ],
2324          ), $params );
2325  
2326      echo '<script type="text/javascript">
2327      var login_icon_load = \'<img src="'.$rsc_url.'img/ajax-loader.gif" alt="'.TS_('Loading...').'" title="'.TS_('Loading...').'" style="margin:2px 0 0 5px" align="top" />\';
2328      var login_icon_available = \''.get_icon( 'allowback', 'imgtag', array( 'title' => TS_('This username is available.') ) ).'\';
2329      var login_icon_exists = \''.get_icon( 'xross', 'imgtag', array( 'title' => TS_('This username is already in use. Please choose another one.') ) ).'\';
2330  
2331      var login_text_empty = \''.TS_('Choose an username.').'\';
2332      var login_text_available = \''.TS_('This username is available.').'\';
2333      var login_text_exists = \''.TS_('This username is already in use. Please choose another one.').'\';
2334  
2335      jQuery( "#register_form #'.$params[ 'login-id' ].'" ).change( function()
2336      {    // Validate if username is available
2337          var note_Obj = jQuery( this ).next().next();
2338          if( jQuery( this ).val() == "" )
2339          {    // Login is empty
2340              jQuery( "#login_status" ).html( "" );
2341              note_Obj.html( login_text_empty ).attr( "class", "notes" );
2342          }
2343          else
2344          {    // Validate login
2345              jQuery( "#login_status" ).html( login_icon_load );
2346              jQuery.ajax( {
2347                  type: "POST",
2348                  url: "'.get_samedomain_htsrv_url().'anon_async.php",
2349                  data: "action=validate_login&login=" + jQuery( this ).val(),
2350                  success: function( result )
2351                  {
2352                      result = ajax_debug_clear( result );
2353                      if( result == "exists" )
2354                      {    // Login already exists
2355                          jQuery( "#login_status" ).html( login_icon_exists );
2356                          note_Obj.html( login_text_exists ).attr( "class", "notes red" );
2357                      }
2358                      else
2359                      {    // Login is available
2360                          jQuery( "#login_status" ).html( login_icon_available );
2361                          note_Obj.html( login_text_available ).attr( "class", "notes green" );
2362                      }
2363                  }
2364              } );
2365          }
2366      } );
2367  </script>';
2368  }
2369  
2370  ?>

title

Description

title

Description

title

Description

title

title

Body