[ Index ]
 

Code source de b2evolution 2.1.0-beta

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/blogs/inc/items/model/ -> _item.class.php (source)

   1  <?php
   2  /**

   3   * This file implements the Item class.

   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-2007 by Francois PLANQUE - {@link http://fplanque.net/}

   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   * @author gorgeb: Bertrand GORGE / EPISTEMA

  32   * @author mbruneau: Marc BRUNEAU / PROGIDISTRI

  33   *

  34   * @version $Id: _item.class.php,v 1.15 2007/11/04 01:10:57 fplanque Exp $

  35   */
  36  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  37  
  38  /**

  39   * Includes:

  40   */
  41  load_class('items/model/_itemlight.class.php');
  42  load_funcs('items/model/_item.funcs.php');
  43  
  44  
  45  /**

  46   * Item Class

  47   *

  48   * @package evocore

  49   */
  50  class Item extends ItemLight
  51  {
  52      /**

  53       * The User who has created the Item (lazy-filled).

  54       * @see Item::get_creator_User()

  55       * @see Item::set_creator_User()

  56       * @var User

  57       * @access protected

  58       */
  59      var $creator_User;
  60  
  61  
  62      /**

  63       * @deprecated by {@link $creator_User}

  64       * @var User

  65       */
  66      var $Author;
  67  
  68  
  69      /**

  70       * ID of the user that created the item

  71       * @var integer

  72       */
  73      var $creator_user_ID;
  74  
  75  
  76      /**

  77       * The assigned User to the item.

  78       * Can be NULL

  79       * @see Item::get_assigned_User()

  80       * @see Item::assign_to()

  81       *

  82       * @var User

  83       * @access protected

  84       */
  85      var $assigned_User;
  86  
  87      /**

  88       * ID of the user that created the item

  89       * Can be NULL

  90       *

  91       * @var integer

  92       */
  93      var $assigned_user_ID;
  94  
  95      /**

  96       * The visibility status of the item.

  97       *

  98       * 'published', 'deprecated', 'protected', 'private' or 'draft'

  99       *

 100       * @var string

 101       */
 102      var $status;
 103      /**

 104       * Locale code for the Item content.

 105       *

 106       * Examples: en-US, zh-CN-utf-8

 107       *

 108       * @var string

 109       */
 110      var $locale;
 111  
 112      var $content;
 113  
 114      /**

 115       * Lazy filled, use split_page()

 116       */
 117      var $content_pages = NULL;
 118  
 119  
 120      var $wordcount;
 121      /**

 122       * The list of renderers, imploded by '.'.

 123       * @var string

 124       * @access protected

 125       */
 126      var $renderers;
 127      /**

 128       * Comments status

 129       *

 130       * "open", "disabled" or "closed

 131       *

 132       * @var string

 133       */
 134      var $comment_status;
 135  
 136      var $pst_ID;
 137      var $datedeadline = '';
 138      var $priority;
 139  
 140      /**

 141       * Have post processing notifications been handled?

 142       * @var string

 143       */
 144      var $notifications_status;
 145      /**

 146       * Which cron task is responsible for handling notifications?

 147       * @var integer

 148       */
 149      var $notifications_ctsk_ID;
 150  
 151      /**

 152       * array of IDs or NULL if we don't know...

 153       *

 154       * @var array

 155       */
 156      var $extra_cat_IDs = NULL;
 157  
 158    /**

 159       * Array of tags (strings)

 160       *

 161       * Lazy loaded.

 162       *

 163       * @var array

 164       */
 165      var $tags = NULL;
 166  
 167      /**

 168       * Array of Links attached to this item.

 169       *

 170       * NULL when not initialized.

 171       *

 172       * @var array

 173       * @access public

 174       */
 175      var $Links = NULL;
 176  
 177  
 178      var $priorities;
 179  
 180      /**

 181       * Pre-rendered content, cached by format/validated renderers.

 182       *

 183       * Can be NULL

 184       *

 185       * @see Item::get_prerendered_content()

 186       * @access protected

 187       * @var array

 188       */
 189      var $content_prerendered;
 190  
 191  
 192      /**

 193       * Constructor

 194       *

 195       * @param object table Database row

 196       * @param string

 197       * @param string

 198       * @param string

 199       * @param string for derived classes

 200       * @param string datetime field name

 201       * @param string datetime field name

 202       * @param string User ID field name

 203       * @param string User ID field name

 204       */
 205  	function Item( $db_row = NULL, $dbtable = 'T_items__item', $dbprefix = 'post_', $dbIDname = 'post_ID', $objtype = 'Item',
 206                     $datecreated_field = 'datecreated', $datemodified_field = 'datemodified',
 207                     $creator_field = 'creator_user_ID', $lasteditor_field = 'lastedit_user_ID' )
 208      {
 209          global $localtimenow, $default_locale, $current_User;
 210  
 211          $this->priorities = array(
 212                  1 => /* TRANS: Priority name */ T_('1 - Highest'),
 213                  2 => /* TRANS: Priority name */ T_('2 - High'),
 214                  3 => /* TRANS: Priority name */ T_('3 - Medium'),
 215                  4 => /* TRANS: Priority name */ T_('4 - Low'),
 216                  5 => /* TRANS: Priority name */ T_('5 - Lowest'),
 217              );
 218  
 219          // Call parent constructor:

 220          parent::ItemLight( $db_row, $dbtable, $dbprefix, $dbIDname, $objtype,
 221                     $datecreated_field, $datemodified_field,
 222                     $creator_field, $lasteditor_field );
 223  
 224          if( is_null($db_row) )
 225          { // New item:
 226              if( isset($current_User) )
 227              { // use current user as default, if available (which won't be the case during install)
 228                  $this->set_creator_User( $current_User );
 229              }
 230              $this->set( 'notifications_status', 'noreq' );
 231              // Set the renderer list to 'default' will trigger all 'opt-out' renderers:

 232              $this->set( 'renderers', array('default') );
 233              $this->set( 'status', 'published' );
 234              $this->set( 'locale', $default_locale );
 235              $this->set( 'priority', 3 );
 236          }
 237          else
 238          {
 239              $this->datecreated = $db_row->post_datecreated; // Needed for history display

 240              $this->creator_user_ID = $db_row->post_creator_user_ID; // Needed for history display

 241              $this->lastedit_user_ID = $db_row->post_lastedit_user_ID; // Needed for history display

 242              $this->assigned_user_ID = $db_row->post_assigned_user_ID;
 243              $this->status = $db_row->post_status;
 244              $this->content = $db_row->post_content;
 245              $this->pst_ID = $db_row->post_pst_ID;
 246              $this->datedeadline = $db_row->post_datedeadline;
 247              $this->priority = $db_row->post_priority;
 248              $this->locale = $db_row->post_locale;
 249              $this->wordcount = $db_row->post_wordcount;
 250              $this->notifications_status = $db_row->post_notifications_status;
 251              $this->notifications_ctsk_ID = $db_row->post_notifications_ctsk_ID;
 252              $this->comment_status = $db_row->post_comment_status;            // Comments status

 253  
 254              // echo 'renderers=', $db_row->post_renderers;

 255              $this->renderers = $db_row->post_renderers;
 256  
 257              $this->views = $db_row->post_views;
 258          }
 259      }
 260  
 261  
 262      /**

 263       * @todo use extended dbchange instead of set_param...

 264       * @todo Normalize to set_assigned_User!?

 265       */
 266  	function assign_to( $user_ID, $dbupdate = true /* BLOAT!? */ )
 267      {
 268          // echo 'assigning user #'.$user_ID;

 269          if( ! empty($user_ID) )
 270          {
 271              if( $dbupdate )
 272              { // Record ID for DB:
 273                  $this->set_param( 'assigned_user_ID', 'number', $user_ID, true );
 274              }
 275              else
 276              {
 277                  $this->assigned_user_ID = $user_ID;
 278              }
 279              $UserCache = & get_Cache( 'UserCache' );
 280              $this->assigned_User = & $UserCache->get_by_ID( $user_ID );
 281          }
 282          else
 283          {
 284              // fp>> DO NOT set (to null) immediately OR it may KILL the current User object (big problem if it's the Current User)

 285              unset( $this->assigned_User );
 286              if( $dbupdate )
 287              { // Record ID for DB:
 288                  $this->set_param( 'assigned_user_ID', 'number', NULL, true );
 289              }
 290              else
 291              {
 292                  $this->assigned_User = NULL;
 293              }
 294              $this->assigned_user_ID = NULL;
 295          }
 296  
 297      }
 298  
 299  
 300      /**

 301       * Template function: display author/creator of item

 302       *

 303       * @param string String to display before author name

 304       * @param string String to display after author name

 305       * @param string Output format, see {@link format_to_output()}

 306       */
 307  	function author( $params = array() )
 308      {
 309          // Make sure we are not missing any param:

 310          $params = array_merge( array(
 311                  'before'      => ' ',
 312                  'after'       => ' ',
 313                  'format'      => 'htmlbody',
 314              ), $params );
 315  
 316          // Load User

 317          $this->get_creator_User();
 318  
 319          echo $params['before'];
 320          echo $this->creator_User->preferred_name( $params['format'], false );
 321          echo $params['after'];
 322      }
 323  
 324  
 325      /**

 326       * Load data from Request form fields.

 327       *

 328       * This requires the blog (e.g. {@link $blog_ID} or {@link $main_cat_ID} to be set).

 329       *

 330       * @param boolean true to force edit date (as long as perms permit)

 331       * @return boolean true if loaded data seems valid.

 332       */
 333  	function load_from_Request( $force_edit_date = false )
 334      {
 335          global $default_locale, $allowed_uri_scheme, $current_User;
 336  
 337          if( param( 'post_locale', 'string', NULL ) !== NULL ) {
 338              $this->set_from_Request( 'locale' );
 339          }
 340  
 341          if( param( 'item_typ_ID', 'integer', NULL ) !== NULL ) {
 342              $this->set_from_Request( 'ptyp_ID', 'item_typ_ID' );
 343          }
 344  
 345          if( param( 'post_url', 'string', NULL ) !== NULL ) {
 346              param_check_url( 'post_url', $allowed_uri_scheme );
 347              $this->set_from_Request( 'url' );
 348          }
 349          // Note: post_url is not part of the simple form, so this message can be a little bit akward there

 350          if( $this->status == 'redirected' && empty($this->url) )
 351          {
 352              param_error( 'post_url', T_('If you want to redirect this post, you must specify an URL! (Expert mode)') );
 353          }
 354  
 355          if( ( $force_edit_date || param( 'edit_date', 'integer', 0 ) )
 356                  && $current_User->check_perm( 'edit_timestamp' ) )
 357          { // We can use user date:
 358              param_date( 'item_issue_date', T_('Please enter a valid issue date.'), $force_edit_date /* required */ );
 359              if( strlen(get_param('item_issue_date')) )
 360              { // only set it, if a date was given:
 361                  param_time( 'item_issue_time' );
 362                  $this->set( 'issue_date', form_date( get_param( 'item_issue_date' ), get_param( 'item_issue_time' ) ) ); // TODO: cleanup...

 363              }
 364          }
 365  
 366          if( param( 'post_excerpt', 'string', NULL ) !== NULL ) {
 367              $this->set_from_Request( 'excerpt' );
 368          }
 369  
 370          if( param( 'post_urltitle', 'string', NULL ) !== NULL ) {
 371              $this->set_from_Request( 'urltitle' );
 372          }
 373  
 374          if( param( 'item_tags', 'string', NULL ) !== NULL ) {
 375              $this->set_tags_from_string( get_param('item_tags') );
 376              // pre_dump( $this->tags );

 377          }
 378  
 379          // Workflow stuff:

 380          if( param( 'item_st_ID', 'integer', NULL ) !== NULL ) {
 381              $this->set_from_Request( 'pst_ID', 'item_st_ID' );
 382          }
 383  
 384          if( param( 'item_assigned_user_ID', 'integer', NULL ) !== NULL ) {
 385              $this->assign_to( get_param('item_assigned_user_ID') );
 386          }
 387  
 388          if( param( 'item_priority', 'integer', NULL ) !== NULL ) {
 389              $this->set_from_Request( 'priority', 'item_priority', true );
 390          }
 391  
 392          if( param_date( 'item_deadline', T_('Please enter a valid deadline.'), false, NULL ) !== NULL ) {
 393              $this->set_from_Request( 'datedeadline', 'item_deadline', true );
 394          }
 395  
 396          // Allow comments for this item (only if set to "post_by_post" for the Blog):

 397          $this->load_Blog();
 398          if( $this->Blog->allowcomments == 'post_by_post' )
 399          {
 400              if( param( 'post_comment_status', 'string', 'open' ) !== NULL )
 401              { // 'open' or 'closed' or ...
 402                  $this->set_from_Request( 'comment_status' );
 403              }
 404          }
 405  
 406          if( param( 'renderers_displayed', 'integer', 0 ) )
 407          { // use "renderers" value only if it has been displayed (may be empty)
 408              $Plugins_admin = & get_Cache('Plugins_admin');
 409              $renderers = $Plugins_admin->validate_renderer_list( param( 'renderers', 'array', array() ) );
 410              $this->set( 'renderers', $renderers );
 411          }
 412          else
 413          {
 414              $renderers = $this->get_renderers_validated();
 415          }
 416  
 417          if( ($content = param( 'content', 'html', NULL )) !== NULL )
 418          {
 419              $post_title = param( 'post_title', 'html', NULL );
 420  
 421              // Do some optional filtering on the content

 422              // Typically stuff that will help the content to validate

 423              // Useful for code display.

 424              // Will probably be used for validation also.

 425              $Plugins_admin = & get_Cache('Plugins_admin');
 426              $Plugins_admin->filter_contents( $post_title /* by ref */, $content /* by ref */, $renderers );
 427  
 428              $this->set( 'content', format_to_post( $content ) );
 429              $this->set( 'title', format_to_post( $post_title ) );
 430          }
 431  
 432          return ! param_errors_detected();
 433      }
 434  
 435  
 436      /**

 437       * Template function: display anchor for permalinks to refer to.

 438       */
 439  	function anchor()
 440      {
 441          global $Settings;
 442  
 443          // In case you have old cafelog permalinks, uncomment the following lines:

 444          // $title = preg_replace( '/[^a-zA-Z0-9_\.-]/', '_', $this->title );

 445          // echo '<a id="'.$title.'"></a>';

 446  
 447          echo '<a id="item_'.$this->ID.'"></a>';
 448      }
 449  
 450  
 451      /**

 452       * Template function: display assignee of item

 453       *

 454       * @param string

 455       * @param string

 456       * @param string Output format, see {@link format_to_output()}

 457       */
 458  	function assigned_to( $before = '', $after = '', $format = 'htmlbody' )
 459      {
 460          if( $this->get_assigned_User() )
 461          {
 462              echo $before;
 463              $this->assigned_User->preferred_name( $format );
 464              echo $after;
 465          }
 466      }
 467  
 468  
 469      /**

 470       * Get list of assigned user options

 471       *

 472       * @uses UserCache::get_blog_member_option_list()

 473       * @return string HTML select options list

 474       */
 475  	function get_assigned_user_options()
 476      {
 477          $UserCache = & get_Cache( 'UserCache' );
 478          return $UserCache->get_blog_member_option_list( $this->blog_ID, $this->assigned_user_ID,
 479                              true,    ($this->ID != 0) /* if this Item is already serialized we'll load the default anyway */ );
 480      }
 481  
 482  
 483      /**

 484       * Check if user can see comments on this post, which he cannot if they

 485       * are disabled for the Item or never allowed for the blog.

 486       *

 487       * @return boolean

 488       */
 489  	function can_see_comments()
 490      {
 491          if( $this->comment_status == 'disabled'
 492              || ( $this->get_Blog() && $this->Blog->allowcomments == 'never' ) )
 493          { // Comments are disabled on this post
 494              return false;
 495          }
 496  
 497          return true; // OK, user can see comments

 498      }
 499  
 500  
 501      /**

 502       * Template function: Check if user can leave comment on this post or display error

 503       *

 504       * @param string|NULL string to display before any error message; NULL to not display anything, but just return boolean

 505       * @param string string to display after any error message

 506       * @param string error message for non published posts, '#' for default

 507       * @param string error message for closed comments posts, '#' for default

 508       * @return boolean true if user can post, false if s/he cannot

 509       */
 510  	function can_comment( $before_error = '<p><em>', $after_error = '</em></p>', $non_published_msg = '#', $closed_msg = '#' )
 511      {
 512          global $Plugins;
 513  
 514          $display = ( ! is_null($before_error) );
 515  
 516          // Ask Plugins (it can say NULL and would get skipped in Plugin::trigger_event_first_return()):

 517          // Examples:

 518          //  - A plugin might want to restrict comments on posts older than 20 days.

 519          //  - A plugin might want to allow comments always for certain users (admin).

 520          if( $event_return = $Plugins->trigger_event_first_return( 'ItemCanComment', array( 'Item' => $this ) ) )
 521          {
 522              $plugin_return_value = $event_return['plugin_return'];
 523              if( $plugin_return_value === true )
 524              {
 525                  return true; // OK, user can comment!

 526              }
 527  
 528              if( $display && is_string($plugin_return_value) )
 529              {
 530                  echo $before_error;
 531                  echo $plugin_return_value;
 532                  echo $after_error;
 533              }
 534  
 535              return false;
 536          }
 537  
 538          if( $this->comment_status == 'disabled'  )
 539          { // Comments are disabled on this post
 540              return false;
 541          }
 542  
 543          if( $this->comment_status == 'closed'  )
 544          { // Comments are closed on this post
 545  
 546              if( $display)
 547              {
 548                  if( $closed_msg == '#' )
 549                      $closed_msg = T_( 'Comments are closed for this post.' );
 550  
 551                  echo $before_error;
 552                  echo $closed_msg;
 553                  echo $after_error;
 554              }
 555  
 556              return false;
 557          }
 558  
 559          if( ($this->status == 'draft') || ($this->status == 'deprecated' ) || ($this->status == 'redirected' ) )
 560          { // Post is not published
 561  
 562              if( $display )
 563              {
 564                  if( $non_published_msg == '#' )
 565                      $non_published_msg = T_( 'This post is not published. You cannot leave comments.' );
 566  
 567                  echo $before_error;
 568                  echo $non_published_msg;
 569                  echo $after_error;
 570              }
 571  
 572              return false;
 573          }
 574  
 575          $this->get_Blog();
 576          if( $this->Blog->allowcomments == 'never')
 577          {
 578              return false;
 579          }
 580  
 581          return true; // OK, user can comment!

 582      }
 583  
 584  
 585    /**

 586       * Template function: Check if user can can rate this post

 587       *

 588       * @return boolean true if user can post, false if s/he cannot

 589       */
 590  	function can_rate()
 591      {
 592          $this->get_Blog();
 593  
 594          if( $this->Blog->get_setting('allow_rating') == 'never' )
 595          {
 596              return false;
 597          }
 598  
 599          return true; // OK, user can rate!

 600      }
 601  
 602  
 603      /**

 604       * Get the prerendered content. If it has not been generated yet, it will.

 605       *

 606       * @todo dh> Currently this makes up one query per displayed item. Probably the cache should get pre-fetched by ItemList2?

 607       * fp> DEFINITELY!!! Preloading all pre-rendered contents for the current Itemlistpage is paramount!

 608       *

 609       * @todo dh> In general, $content_prerendered gets only queried once per item, so it seems like a memory waste to cache the query result..!

 610       * fp> I don't know if this is supposed to be related but that doesn't change anything to the previous todo.

 611       *

 612       * NOTE: This calls {@link Item::dbupdate()}, if renderers get changed (from Plugin hook).

 613       *

 614       * @param string Format, see {@link format_to_output()}.

 615       *        Only "htmlbody", "entityencoded", "xml" and "text" get cached.

 616       * @return string

 617       */
 618  	function get_prerendered_content( $format )
 619      {
 620          global $Plugins;
 621  
 622          $post_renderers = $this->get_renderers_validated();
 623          $cache_key = $format.'/'.implode('.', $post_renderers);
 624  
 625          if( ! isset( $this->content_prerendered[$cache_key] ) )
 626          {
 627              $use_cache = $this->ID && in_array( $format, array( 'htmlbody', 'entityencoded', 'xml', 'text' ) );
 628  
 629              // $use_cache = false;

 630  
 631              if( $use_cache )
 632              { // the format/item can be cached:
 633                  global $DB;
 634  
 635                  $cache = $DB->get_var( "
 636                      SELECT itpr_content_prerendered
 637                          FROM T_items__prerendering
 638                       WHERE itpr_itm_ID = ".$this->ID."
 639                           AND itpr_format = '".$format."'
 640                         AND itpr_renderers = '".implode('.', $post_renderers)."'", 0, 0, 'Check prerendered item content' );
 641  
 642                  if( $cache !== NULL ) // may be empty string
 643                  { // Retrieved from cache:
 644                      // echo ' retrieved from prerendered cache';

 645                      $this->content_prerendered[$cache_key] = $cache;
 646                  }
 647              }
 648  
 649              if( ! isset( $this->content_prerendered[$cache_key] ) )
 650              {    // Not cached yet:
 651                  global $Debuglog;
 652  
 653                  if( $this->update_renderers_from_Plugins() )
 654                  {
 655                      $post_renderers = $this->get_renderers_validated(); // might have changed from call above

 656                      $cache_key = $format.'/'.implode('.', $post_renderers);
 657  
 658                      // Save new renderers with item:

 659                      $this->dbupdate();
 660                  }
 661  
 662                  // Call RENDERER plugins:

 663                  // pre_dump( $this->content );

 664                  $this->content_prerendered[$cache_key] = $this->content;
 665                  $Plugins->render( $this->content_prerendered[$cache_key] /* by ref */, $post_renderers, $format, array( 'Item' => $this ), 'Render' );
 666                  // pre_dump( $this->content_prerendered[$cache_key] );

 667  
 668                  $Debuglog->add( 'Generated pre-rendered content ['.$cache_key.'] for item #'.$this->ID, 'items' );
 669  
 670                  if( $use_cache )
 671                  { // save into DB (using REPLACE INTO because it may have been pre-rendered by another thread since the SELECT above)
 672                      $DB->query( "
 673                          REPLACE INTO T_items__prerendering (itpr_itm_ID, itpr_format, itpr_renderers, itpr_content_prerendered)
 674                           VALUES ( ".$this->ID.", '".$format."', ".$DB->quote(implode('.', $post_renderers)).', '.$DB->quote($this->content_prerendered[$cache_key]).' )', 'Cache prerendered item content' );
 675                  }
 676              }
 677          }
 678          else
 679          {
 680              $Debuglog->add( 'Fetched pre-rendered content ['.$cache_key.'] for item #'.$this->ID, 'items' );
 681          }
 682  
 683          return $this->content_prerendered[$cache_key];
 684      }
 685  
 686  
 687      /**

 688       * Set the pre-rendered content.

 689       *

 690       * This is meant to get called by ItemList2, which would do a single query for all

 691       * items.

 692       *

 693       * @param string Pre-rendered content

 694       * @param string Cache-Key ($format.'/'.$renderers). See {@link Item::get_prerendered_content()} for the appropriate query skeleton.

 695       */
 696  	function set_prerendered_content( $content, $cache_key )
 697      {
 698          $this->content_prerendered[$cache_key] = $content;
 699      }
 700  
 701  
 702      /**

 703       * Trigger {@link Plugin::ItemApplyAsRenderer()} event and adjust renderers according

 704       * to return value.

 705       * @return boolean True if renderers got changed.

 706       */
 707  	function update_renderers_from_Plugins()
 708      {
 709          global $Plugins;
 710  
 711          $r = false;
 712  
 713          foreach( $Plugins->get_list_by_event('ItemApplyAsRenderer') as $Plugin )
 714          {
 715              if( empty($Plugin->code) )
 716                  continue;
 717  
 718              $plugin_r = $Plugin->ItemApplyAsRenderer( $tmp_params = array('Item' => & $this) );
 719  
 720              if( is_bool($plugin_r) )
 721              {
 722                  if( $plugin_r )
 723                  {
 724                      $r = $this->add_renderer( $Plugin->code ) || $r;
 725                  }
 726                  else
 727                  {
 728                      $r = $this->remove_renderer( $Plugin->code ) || $r;
 729                  }
 730              }
 731          }
 732  
 733          return $r;
 734      }
 735  
 736  
 737      /**

 738       * Make sure, the pages have been obtained (and split up_ from prerendered cache.

 739       *

 740       * @param string Format, used to retrieve the matching cache; see {@link format_to_output()}

 741       */
 742  	function split_pages( $format = 'htmlbody' )
 743      {
 744          if( ! isset( $this->content_pages[$format] ) )
 745          {
 746              // SPLIT PAGES:

 747              $this->content_pages[$format] = explode( '<!--nextpage-->', $this->get_prerendered_content($format) );
 748  
 749              $this->pages = count( $this->content_pages[$format] );
 750              // echo ' Pages:'.$this->pages;

 751          }
 752      }
 753  
 754  
 755      /**

 756       * Get a specific page to display (from the prerendered cache)

 757       *

 758       * @param integer Page number

 759       * @param string Format, used to retrieve the matching cache; see {@link format_to_output()}

 760       */
 761  	function get_content_page( $page, $format = 'htmlbody' )
 762      {
 763          // Make sure, the pages are split up:

 764          $this->split_pages( $format );
 765  
 766          if( $page < 1 )
 767          {
 768              $page = 1;
 769          }
 770  
 771          if( $page > $this->pages )
 772          {
 773              $page = $this->pages;
 774          }
 775  
 776          return $this->content_pages[$format][$page-1];
 777      }
 778  
 779  
 780    /**

 781       * This is like a teaser with no HTML and a cropping.

 782       *

 783       * @todo fp> allow use to submit his own excerpt in expert editing mode

 784       */
 785  	function get_content_excerpt( $crop_at = 200 )
 786      {
 787          // Get teaser for page 1:

 788          // fp> Note: I'm not sure about using 'text' here, but there should definitely be no rendering here.

 789          $output = $this->get_content_teaser( 1, false, 'text' );
 790  
 791          // Get rid of all HTML:

 792          $output = strip_tags( $output );
 793  
 794          // Ger rid of all new lines:

 795          $output = trim( str_replace( array( "\r", "\n", "\t" ), array( ' ', ' ', ' ' ), $output ) );
 796  
 797          if( strlen( $output ) > $crop_at )
 798          {
 799              $output = substr( $output, 0, $crop_at ).'...';
 800          }
 801  
 802          return $output;
 803      }
 804  
 805  
 806      /**

 807       * Display content teaser of item (will stop at "<!-- more -->"

 808       */
 809  	function content_teaser( $params )
 810      {
 811          // Make sure we are not missing any param:

 812          $params = array_merge( array(
 813                  'before'      => '',
 814                  'after'       => '',
 815                  'disppage'    => '#',
 816                  'stripteaser' => '#',
 817                  'format'      => 'htmlbody',
 818              ), $params );
 819  
 820          $r = $this->get_content_teaser( $params['disppage'], $params['stripteaser'], $params['format'] );
 821  
 822          if( !empty($r) )
 823          {
 824              echo $params['before'];
 825              echo $r;
 826              echo $params['after'];
 827          }
 828      }
 829  
 830      /**

 831       * Template function: get content teaser of item (will stop at "<!-- more -->"

 832       *

 833       * @param mixed page number to display specific page, # for url parameter

 834       * @param boolean # if you don't want to repeat teaser after more link was pressed and <-- noteaser --> has been found

 835       * @param string filename to use to display more

 836       * @return string

 837       */
 838  	function get_content_teaser( $disppage = '#', $stripteaser = '#', $format = 'htmlbody' )
 839      {
 840          global $Plugins, $preview, $Debuglog;
 841          global $more;
 842  
 843          // Get requested content page:

 844          if( $disppage === '#' )
 845          { // We want to display the page requested by the user:
 846              global $page;
 847              $disppage = $page;
 848          }
 849  
 850          $content_page = $this->get_content_page( $disppage, $format ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml"

 851          // pre_dump($content_page);

 852  
 853          $content_parts = explode( '<!--more-->', $content_page );
 854          // echo ' Parts:'.count($content_parts);

 855  
 856          if( count($content_parts) > 1 )
 857          { // This is an extended post (has a more section):
 858              if( $stripteaser === '#' )
 859              {
 860                  // If we're in "more" mode and we want to strip the teaser, we'll strip:

 861                  $stripteaser = ( $more && preg_match('/<!--noteaser-->/', $content_page ) );
 862              }
 863  
 864              if( $stripteaser )
 865              {
 866                  return NULL;
 867              }
 868          }
 869  
 870          $output = $content_parts[0];
 871  
 872          // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED:

 873          $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array(
 874                  'Item' => $this,
 875                  'preview' => $preview,
 876                  'dispmore' => ($more != 0),
 877              ), 'Display' );
 878  
 879          // Character conversions

 880          $output = format_to_output( $output, $format );
 881  
 882          return $output;
 883      }
 884  
 885  
 886      /**

 887       * Display content teaser of item (will stop at "<!-- more -->"

 888       */
 889  	function content_extension( $params )
 890      {
 891          // Make sure we are not missing any param:

 892          $params = array_merge( array(
 893                  'before'      => '',
 894                  'after'       => '',
 895                  'disppage'    => '#',
 896                  'format'      => 'htmlbody',
 897                  'force_more'  => false,
 898              ), $params );
 899  
 900          $r = $this->get_content_extension( $params['disppage'], $params['force_more'], $params['format'] );
 901  
 902          if( !empty($r) )
 903          {
 904              echo $params['before'];
 905              echo $r;
 906              echo $params['after'];
 907          }
 908      }
 909  
 910  
 911      /**

 912       * Template function: get content extension of item (part after "<!-- more -->")

 913       *

 914       * @param mixed page number to display specific page, # for url parameter

 915       * @param boolean

 916       * @param string filename to use to display more

 917       * @return string

 918       */
 919  	function get_content_extension( $disppage = '#', $force_more = false, $format = 'htmlbody' )
 920      {
 921          global $Plugins, $more, $preview;
 922  
 923          if( ! $more && ! $force_more )
 924          {    // NOT in more mode:
 925              return NULL;
 926          }
 927  
 928          // Get requested content page:

 929          if( $disppage === '#' )
 930          { // We want to display the page requested by the user:
 931              global $page;
 932              $disppage = $page;
 933          }
 934  
 935          $content_page = $this->get_content_page( $disppage, $format ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml"

 936          // pre_dump($content_page);

 937  
 938          $content_parts = explode( '<!--more-->', $content_page );
 939          // echo ' Parts:'.count($content_parts);

 940  
 941          if( count($content_parts) < 2 )
 942          { // This is NOT an extended post
 943              return NULL;
 944          }
 945  
 946          // Output everything after <!-- more -->

 947          array_shift($content_parts);
 948          $output = implode('', $content_parts);
 949  
 950          // Trigger Display plugins FOR THE STUFF THAT WOULD NOT BE PRERENDERED:

 951          $output = $Plugins->render( $output, $this->get_renderers_validated(), $format, array(
 952                  'Item' => $this,
 953                  'preview' => $preview,
 954                  'dispmore' => true,
 955              ), 'Display' );
 956  
 957          // Character conversions

 958          $output = format_to_output( $output, $format );
 959  
 960          return $output;
 961      }
 962  
 963  
 964    /**

 965       * Increase view counter

 966       *

 967       * @todo merge with inc_viewcount

 968       */
 969  	function count_view( $params = array() )
 970      {
 971          // Make sure we are not missing any param:

 972          $params = array_merge( array(
 973                  'allow_multiple_counts_per_page' => false,
 974              ), $params );
 975  
 976  
 977          global $Hit, $preview, $Debuglog;
 978  
 979          if( $preview )
 980          {
 981              // echo 'PREVIEW';

 982              return false;
 983          }
 984  
 985          /*

 986           * Check if we want to increment view count, see {@link Hit::is_new_view()}

 987           */
 988          if( ! $Hit->is_new_view() )
 989          {    // This is a reload
 990              // echo 'RELOAD';

 991              return false;
 992          }
 993  
 994          if( ! $params['allow_multiple_counts_per_page'] )
 995          {    // Check that we don't increase multiple viewcounts on the same page
 996              // This make the assumption that the first post in a list is "viewed" and the other are not (necesarily)

 997              global $view_counts_on_this_page;
 998              if( $view_counts_on_this_page >= 1 )
 999              {    // we already had a count on this page
1000                  // echo 'ALREADY HAD A COUNT';

1001                  return false;
1002              }
1003              $view_counts_on_this_page++;
1004          }
1005  
1006          //echo 'COUNTING VIEW';

1007  
1008      // Increment view counter (only if current User is not the item's author)

1009          return $this->inc_viewcount(); // won't increment if current_User == Author

1010  
1011      }
1012  
1013  
1014      /**

1015       * Template tag

1016       */
1017  	function more_link( $params = array() )
1018      {
1019          echo $this->get_more_link( $params );
1020      }
1021  
1022  
1023    /**

1024       * Display more link

1025       *

1026       * @param string string to display before more link

1027       * @param string string to display after more link

1028       * @param string

1029       * @param string

1030       * @param mixed

1031       * @param string Output format, see {@link format_to_output()}

1032       */
1033  	function get_more_link( $params = array() )
1034      {
1035          // Make sure we are not missing any param:

1036          $params = array_merge( array(
1037                  'before'      => '<p class="bMore">',
1038                  'after'       => '</p>',
1039                  'link_text'   => '#',        // text to display as the more link
1040                  'anchor_text' => '#',        // text to display as the more anchor (once the more link has been clicked)
1041                  'disppage'    => '#',        // page number to display specific page, # for url parameter
1042                  'format'      => 'htmlbody',
1043              ), $params );
1044  
1045          global $more;
1046  
1047          // Get requested content page:

1048          if( $params['disppage'] === '#' )
1049          { // We want to display the page requested by the user:
1050              global $page;
1051              $params['disppage'] = $page;
1052          }
1053  
1054          $content_page = $this->get_content_page( $params['disppage'], $params['format'] ); // cannot include format_to_output() because of the magic below.. eg '<!--more-->' will get stripped in "xml"

1055          // pre_dump($content_page);

1056  
1057          $content_parts = explode( '<!--more-->', $content_page );
1058          // echo ' Parts:'.count($content_parts);

1059  
1060          if( count($content_parts) < 2 )
1061          { // This is NOT an extended post:
1062              return '';
1063          }
1064  
1065          if( ! $more )
1066          {    // We're NOT in "more" mode:
1067              if( $params['link_text'] == '#' )
1068              { // TRANS: this is the default text for the extended post "more" link
1069                  $params['link_text'] = T_('Read more').' &raquo;';
1070              }
1071  
1072              return format_to_output( $params['before']
1073                          .'<a href="'.$this->get_permanent_url().'#more'.$this->ID.'">'
1074                          .$params['link_text'].'</a>'
1075                          .$params['after'], $params['format'] );
1076          }
1077          elseif( ! preg_match('/<!--noteaser-->/', $content_page ) )
1078          {    // We are in mode mode and we're not hiding the teaser:
1079              // (if we're higin the teaser we display this as a normal page ie: no anchor)

1080              if( $params['anchor_text'] == '#' )
1081              { // TRANS: this is the default text displayed once the more link has been activated
1082                  $params['anchor_text'] = '<p class="bMore">'.T_('Follow up:').'</p>';
1083              }
1084  
1085              return format_to_output( '<a id="more'.$this->ID.'" name="more'.$this->ID.'"></a>'
1086                              .$params['anchor_text'], $params['format'] );
1087          }
1088      }
1089  
1090  
1091      /**

1092       * Template function: display deadline date (datetime) of Item

1093       *

1094       * @param string date/time format: leave empty to use locale default date format

1095       * @param boolean true if you want GMT

1096       */
1097  	function deadline_date( $format = '', $useGM = false )
1098      {
1099          if( empty($format) )
1100              echo mysql2date( locale_datefmt(), $this->datedeadline, $useGM);
1101          else
1102              echo mysql2date( $format, $this->datedeadline, $useGM);
1103      }
1104  
1105  
1106      /**

1107       * Template function: display deadline time (datetime) of Item

1108       *

1109       * @param string date/time format: leave empty to use locale default time format

1110       * @param boolean true if you want GMT

1111       */
1112  	function deadline_time( $format = '', $useGM = false )
1113      {
1114          if( empty($format) )
1115              echo mysql2date( locale_timefmt(), $this->datedeadline, $useGM );
1116          else
1117              echo mysql2date( $format, $this->datedeadline, $useGM );
1118      }
1119  
1120  
1121      /**

1122       * Get reference to array of Links

1123       */
1124      function & get_Links()
1125      {
1126          // Make sure links are loaded:

1127          $this->load_links();
1128  
1129          return $this->Links;
1130      }
1131  
1132  
1133      /**

1134       * Template function: display number of links attached to this Item

1135       */
1136  	function linkcount()
1137      {
1138          // Make sure links are loaded:

1139          $this->load_links();
1140  
1141          echo count($this->Links);
1142      }
1143  
1144  
1145      /**

1146       * Load links if they were not loaded yet.

1147       */
1148  	function load_links()
1149      {
1150          if( is_null( $this->Links ) )
1151          { // Links have not been loaded yet:
1152              $LinkCache = & get_Cache( 'LinkCache' );
1153              $this->Links = & $LinkCache->get_by_item_ID( $this->ID );
1154          }
1155      }
1156  
1157  
1158    /**

1159       * Get array of tags

1160       *

1161       * Load from DB if necessary

1162       *

1163       * @return array

1164       */
1165      function & get_tags()
1166      {
1167          global $DB;
1168  
1169          if( !isset( $this->tags ) )
1170          {
1171              $this->tags = $DB->get_col(
1172                                              'SELECT tag_name
1173                                                    FROM T_items__itemtag INNER JOIN T_items__tag ON itag_tag_ID = tag_ID
1174                                                   WHERE itag_itm_ID = '.$this->ID.'
1175                                                   ORDER BY tag_name', 0, 'Get tags for Item' );
1176          }
1177  
1178          // pre_dump( $this->tags );

1179          return $this->tags;
1180      }
1181  
1182  
1183    /**

1184       * Split tags by space or comma

1185       *

1186       * @todo fp> allow tags with spaces when quoted like "long tag". Nota comma should never be allowed in a tag.

1187        *

1188        * @param string

1189       */
1190  	function set_tags_from_string( $tags )
1191      {
1192          if( empty($tags) )
1193          {
1194              $this->tags = array();
1195          }
1196          else
1197          {
1198              $this->tags = preg_split( '/[\s,]+/', $tags );
1199          }
1200          // pre_dump( $this->tags );

1201      }
1202  
1203  
1204      /**

1205       * Template function: Provide link to message form for this Item's author.

1206       *

1207       * @param string url of the message form

1208       * @param string to display before link

1209       * @param string to display after link

1210       * @param string link text

1211       * @param string link title

1212       * @param string class name

1213       * @return boolean true, if a link was displayed; false if there's no email address for the Item's author.

1214       */
1215  	function msgform_link( $params = array() )
1216      {
1217          // Make sure we are not missing any param:

1218          $params = array_merge( array(
1219                  'before'      => ' ',
1220                  'after'       => ' ',
1221                  'text'        => '#',
1222                  'title'       => '#',
1223                  'class'       => '',
1224                  'format'      => 'htmlbody',
1225                  'form_url'    => '#current_blog#',
1226              ), $params );
1227  
1228          $this->get_creator_User();
1229  
1230          if( empty($this->creator_User->email) )
1231          { // We have no email for this Author :(
1232              return false;
1233          }
1234          if( empty($this->creator_User->allow_msgform) )
1235          {
1236              return false;
1237          }
1238  
1239          if( $params['form_url'] == '#current_blog#' )
1240          {    // Get
1241              global $Blog;
1242              $params['form_url'] = $Blog->get('msgformurl');
1243          }
1244  
1245          $params['form_url'] = url_add_param( $params['form_url'], 'recipient_id='.$this->creator_User->ID.'&amp;post_id='.$this->ID
1246              .'&amp;redirect_to='.rawurlencode(url_rel_to_same_host(regenerate_url('','','','&'), $params['form_url'])) );
1247  
1248          if( $params['title'] == '#' ) $params['title'] = T_('Send email to post author');
1249          if( $params['text'] == '#' ) $params['text'] = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $params['title'] ) );
1250  
1251          echo $params['before'];
1252          echo '<a href="'.$params['form_url'].'" title="'.$params['title'].'"';
1253          if( !empty( $params['class'] ) ) echo ' class="'.$params['class'].'"';
1254          echo '>'.$params['text'].'</a>';
1255          echo $params['after'];
1256  
1257          return true;
1258      }
1259  
1260  
1261      /**

1262       * Template function: Provide link to message form for this Item's assigned User.

1263       *

1264       * @param string url of the message form

1265       * @param string to display before link

1266       * @param string to display after link

1267       * @param string link text

1268       * @param string link title

1269       * @param string class name

1270       * @return boolean true, if a link was displayed; false if there's no email address for the assigned User.

1271       */
1272  	function msgform_link_assigned( $form_url, $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' )
1273      {
1274          if( ! $this->get_assigned_User() || empty($this->assigned_User->email) )
1275          { // We have no email for this Author :(
1276              return false;
1277          }
1278  
1279          $form_url = url_add_param( $form_url, 'recipient_id='.$this->assigned_User->ID );
1280          $form_url = url_add_param( $form_url, 'post_id='.$this->ID );
1281  
1282          if( $title == '#' ) $title = T_('Send email to assigned user');
1283          if( $text == '#' ) $text = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $title ) );
1284  
1285          echo $before;
1286          echo '<a href="'.$form_url.'" title="'.$title.'"';
1287          if( !empty( $class ) ) echo ' class="'.$class.'"';
1288          echo '>'.$text.'</a>';
1289          echo $after;
1290  
1291          return true;
1292      }
1293  
1294  
1295      /**

1296       *

1297       */
1298  	function page_links( $before = '#', $after = '#', $separator = ' ', $single = '', $current_page = '#', $pagelink = '%d', $url = '' )
1299      {
1300  
1301          // Make sure, the pages are split up:

1302          $this->split_pages();
1303  
1304          if( $this->pages <= 1 )
1305          {    // Single page:
1306              echo $single;
1307              return;
1308          }
1309  
1310          if( $before == '#' ) $before = '<p>'.T_('Pages:').' ';
1311          if( $after == '#' ) $after = '</p>';
1312  
1313          if( $current_page == '#' )
1314          {
1315              global $page;
1316              $current_page = $page;
1317          }
1318  
1319          if( empty($url) )
1320          {
1321              $url = $this->get_permanent_url( '', '', '&amp;' );
1322          }
1323  
1324          $page_links = array();
1325  
1326          for( $i = 1; $i <= $this->pages; $i++ )
1327          {
1328              $text = str_replace('%d', $i, $pagelink);
1329  
1330              if( $i != $current_page )
1331              {
1332                  if( $i == 1 )
1333                  {    // First page special:
1334                      $page_links[] = '<a href="'.$url.'">'.$text.'</a>';
1335                  }
1336                  else
1337                  {
1338                      $page_links[] = '<a href="'.url_add_param( $url, 'page='.$i ).'">'.$text.'</a>';
1339                  }
1340              }
1341              else
1342              {
1343                  $page_links[] = $text;
1344              }
1345          }
1346  
1347          echo $before;
1348          echo implode( $separator, $page_links );
1349          echo $after;
1350      }
1351  
1352  
1353      /**

1354       * Display the images linked to the current Item

1355       *

1356       * @param array of params

1357       * @param string Output format, see {@link format_to_output()}

1358       */
1359  	function images( $params = array(), $format = 'htmlbody' )
1360      {
1361          echo $this->get_images( $params, $format );
1362      }
1363  
1364  
1365      /**

1366       * Get block of images linked to the current Item

1367       *

1368       * @param array of params

1369       * @param string Output format, see {@link format_to_output()}

1370       */
1371  	function get_images( $params = array(), $format = 'htmlbody' )
1372      {
1373          $params = array_merge( array(
1374                  'before' =>              '<div>',
1375                  'before_image' =>        '<div class="image_block">',
1376                  'before_image_legend' => '<div class="image_legend">',
1377                  'after_image_legend' =>  '</div>',
1378                  'after_image' =>         '</div>',
1379                  'after' =>               '</div>',
1380                  'image_size' =>          'fit-720x500'
1381              ), $params );
1382  
1383          $FileCache = & get_Cache( 'FileCache' );
1384  
1385          $FileList = & new DataObjectList2( $FileCache );
1386  
1387  
1388          $SQL = & new SQL();
1389          $SQL->SELECT( 'file_ID, file_title, file_root_type, file_root_ID, file_path, file_alt, file_desc' );
1390          $SQL->FROM( 'T_links INNER JOIN T_files ON link_file_ID = file_ID' );
1391          $SQL->WHERE( 'link_itm_ID = '.$this->ID );
1392          $SQL->ORDER_BY( 'link_ID' );
1393  
1394          $FileList->sql = $SQL->get();
1395  
1396          $FileList->query( false, false, false );
1397  
1398          $r = '';
1399      /**

1400           * @var File

1401           */
1402          $File = NULL;
1403          while( $File = & $FileList->get_next() )
1404          {
1405              if( ! $File->is_image() )
1406              {    // Skip anything that is not an image
1407                  // fp> TODO: maybe this property should be stored in link_ltype_ID

1408                  continue;
1409              }
1410              // Generate the IMG tag with all the alt, title and desc if available

1411              $r .= $File->get_tag( $params['before_image'], $params['before_image_legend'], $params['after_image_legend'], $params['after_image'], $params['image_size'] );
1412          }
1413  
1414          if( !empty($r) )
1415          {
1416              $r = $params['before'].$r.$params['after'];
1417  
1418              // Character conversions

1419              $r = format_to_output( $r, $format );
1420          }
1421  
1422          return $r;
1423      }
1424  
1425  
1426      /**

1427       * Template function: Displays link to the feed for comments on this item

1428       *

1429       * @param string Type of feedback to link to (rss2/atom)

1430       * @param string String to display before the link (if comments are to be displayed)

1431       * @param string String to display after the link (if comments are to be displayed)

1432       * @param string Link title

1433       */
1434  	function feedback_feed_link( $skin = '_rss2', $before = '', $after = '', $title='#' )
1435      {
1436          if( ! $this->can_see_comments() )
1437          {    // Comments disabled
1438              return;
1439          }
1440  
1441          if( $title == '#' )
1442          {
1443              $title = get_icon( 'feed' ).' '.T_('Comment feed for this post');
1444          }
1445  
1446          $url = $this->get_feedback_feed_url($skin);
1447  
1448          echo $before;
1449          echo '<a href="'.$url.'">'.format_to_output($title).'</a>';
1450          echo $after;
1451      }
1452  
1453  
1454      /**

1455       * Get URL to display the post comments in an XML feed.

1456       *

1457       * @param string

1458       */
1459  	function get_feedback_feed_url( $skin_folder_name )
1460      {
1461          $this->load_Blog();
1462  
1463          return url_add_param( $this->Blog->get_tempskin_url( $skin_folder_name ), 'disp=comments&amp;p='.$this->ID);
1464      }
1465  
1466  
1467      /**

1468       * Template function: Displays link to feedback page (under some conditions)

1469       *

1470       * @param array

1471       */
1472  	function feedback_link( $params )
1473      {
1474          if( ! $this->can_see_comments() )
1475          {    // Comments disabled
1476              return;
1477          }
1478  
1479          $params = array_merge( array(
1480                                      'type' => 'feedbacks',
1481                                      'status' => 'published',
1482                                      'link_before' => '',
1483                                      'link_after' => '',
1484                                      'link_text_zero' => '#',
1485                                      'link_text_one' => '#',
1486                                      'link_text_more' => '#',
1487                                      'link_title' => '#',
1488                                      'use_popup' => false,
1489                                      'url' => '#',
1490                                  ), $params );
1491  
1492  
1493          // dh> TODO:    Add plugin hook, where a Pingback plugin could hook and provide "pingbacks"

1494          switch( $params['type'] )
1495          {
1496              case 'feedbacks':
1497                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display feedback / Leave a comment');
1498                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send feedback').' &raquo;';
1499                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 feedback').' &raquo;';
1500                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d feedbacks').' &raquo;';
1501                  break;
1502  
1503              case 'comments':
1504                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display comments / Leave a comment');
1505                  if( $params['link_text_zero'] == '#' )
1506                  {
1507                      if( $this->can_comment( NULL ) ) // NULL, because we do not want to display errors here!
1508                      {
1509                          $params['link_text_zero'] = T_('Leave a comment').' &raquo;';
1510                      }
1511                      else
1512                      {
1513                          $params['link_text_zero'] = '';
1514                      }
1515                  }
1516                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 comment').' &raquo;';
1517                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d comments').' &raquo;';
1518                  break;
1519  
1520              case 'trackbacks':
1521                  $this->get_Blog();
1522                  if( ! $this->Blog->get( 'allowtrackbacks' ) )
1523                  { // Trackbacks not allowed on this blog:
1524                      return;
1525                  }
1526                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display trackbacks / Get trackback address for this post');
1527                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('Send a trackback').' &raquo;';
1528                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 trackback').' &raquo;';
1529                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d trackbacks').' &raquo;';
1530                  break;
1531  
1532              case 'pingbacks':
1533                  // Obsolete, but left for skin compatibility

1534                  $this->get_Blog();
1535                  if( ! $this->Blog->get( 'allowtrackbacks' ) )
1536                  { // Trackbacks not allowed on this blog:
1537                      // We'll consider pingbacks to follow the same restriction

1538                      return;
1539                  }
1540                  if( $params['link_title'] == '#' ) $params['link_title'] = T_('Display pingbacks');
1541                  if( $params['link_text_zero'] == '#' ) $params['link_text_zero'] = T_('No pingback yet').' &raquo;';
1542                  if( $params['link_text_one'] == '#' ) $params['link_text_one'] = T_('1 pingback').' &raquo;';
1543                  if( $params['link_text_more'] == '#' ) $params['link_text_more'] = T_('%d pingbacks').' &raquo;';
1544                  break;
1545  
1546              default:
1547                  debug_die( "Unknown feedback type [{$params['type']}]" );
1548          }
1549  
1550          $link_text = $this->get_feedback_title( $params['type'], $params['link_text_zero'], $params['link_text_one'], $params['link_text_more'], $params['status'] );
1551  
1552          if( empty($link_text) )
1553          {    // No link, no display...
1554              return false;
1555          }
1556  
1557          if( $params['url'] == '#' )
1558          { // We want a link to single post:
1559              $params['url'] = $this->get_single_url( 'auto' );
1560          }
1561  
1562  
1563          echo $params['link_before'];
1564  
1565          if( !empty( $params['url'] ) )
1566          {
1567              echo '<a href="'.$params['url'];
1568              echo '#'.$params['type'].'" ';    // Position on feedback

1569              echo 'title="'.$params['link_title'].'"';
1570              if( $params['use_popup'] )
1571              {    // Special URL if we can open a popup (i-e if JS is enabled):
1572                  $popup_url = url_add_param( $params['url'], 'disp=feedback-popup' );
1573                  echo ' onclick="return pop_up_window( \''.$popup_url.'\', \'evo_comments\' )"';
1574              }
1575              echo '>';
1576              echo $link_text;
1577              echo '</a>';
1578          }
1579          else
1580          {
1581              echo $link_text;
1582          }
1583  
1584          echo $params['link_after'];
1585      }
1586  
1587  
1588      /**

1589       * Get text depending on number of comments

1590       *

1591       * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks)

1592       * @param string Link text to display when there are 0 comments

1593       * @param string Link text to display when there is 1 comment

1594       * @param string Link text to display when there are >1 comments (include %d for # of comments)

1595       * @param string Status of feedbacks to count

1596       */
1597  	function get_feedback_title( $type = 'feedbacks',    $zero = '#', $one = '#', $more = '#', $status = 'published' )
1598      {
1599          if( ! $this->can_see_comments() )
1600          {    // Comments disabled
1601              return NULL;
1602          }
1603  
1604          // dh> TODO:    Add plugin hook, where a Pingback plugin could hook and provide "pingbacks"

1605          switch( $type )
1606          {
1607              case 'feedbacks':
1608                  if( $zero == '#' ) $zero = '';
1609                  if( $one == '#' ) $one = T_('1 feedback');
1610                  if( $more == '#' ) $more = T_('%d feedbacks');
1611                  break;
1612  
1613              case 'comments':
1614                  if( $zero == '#' ) $zero = '';
1615                  if( $one == '#' ) $one = T_('1 comment');
1616                  if( $more == '#' ) $more = T_('%d comments');
1617                  break;
1618  
1619              case 'trackbacks':
1620                  if( $zero == '#' ) $zero = '';
1621                  if( $one == '#' ) $one = T_('1 trackback');
1622                  if( $more == '#' ) $more = T_('%d trackbacks');
1623                  break;
1624  
1625              case 'pingbacks':
1626                  // Obsolete, but left for skin compatibility

1627                  if( $zero == '#' ) $zero = '';
1628                  if( $one == '#' ) $one = T_('1 pingback');
1629                  if( $more == '#' ) $more = T_('%d pingbacks');
1630                  break;
1631  
1632              default:
1633                  debug_die( "Unknown feedback type [$type]" );
1634          }
1635  
1636          $number = generic_ctp_number( $this->ID, $type, $status );
1637  
1638          if( $number == 0 )
1639              return $zero;
1640          elseif( $number == 1 )
1641              return $one;
1642          elseif( $number > 1 )
1643              return str_replace( '%d', $number, $more );
1644      }
1645  
1646  
1647      /**

1648       * Template function: Displays feeback moderation info

1649       *

1650       * @param string Type of feedback to link to (feedbacks (all)/comments/trackbacks/pingbacks)

1651       * @param string String to display before the link (if comments are to be displayed)

1652       * @param string String to display after the link (if comments are to be displayed)

1653       * @param string Link text to display when there are 0 comments

1654       * @param string Link text to display when there is 1 comment

1655       * @param string Link text to display when there are >1 comments (include %d for # of comments)

1656       * @param string Link

1657       * @param boolean true to hide if no feedback

1658       */
1659  	function feedback_moderation( $type = 'feedbacks', $before = '', $after = '',
1660                                                      $zero = '', $one = '#', $more = '#', $edit_comments_link = '#', $params = array() )
1661      {
1662          /**

1663           * @var User

1664           */
1665          global $current_User;
1666  
1667      $params = array_merge( array(
1668                                      'type' => 'feedbacks',
1669                                      'block_before' => '',
1670                                      'blo_after' => '',
1671                                      'link_text_zero' => '#',
1672                                      'link_text_one' => '#',
1673                                      'link_text_more' => '#',
1674                                      'link_title' => '#',
1675                                      'use_popup' => false,
1676                                      'url' => '#',
1677                                  'type' => 'feedbacks',
1678                                      '' => '',
1679                                  ), $params );
1680  
1681          if( isset($current_User) && $current_User->check_perm( 'blog_comments', 'any', false,    $this->blog_ID ) )
1682          {    // We jave permission to edit comments:
1683              if( $edit_comments_link == '#' )
1684              {    // Use default link:
1685                  global $admin_url;
1686                  $edit_comments_link = '<a href="'.$admin_url.'?ctrl=items&amp;blog='.$this->blog_ID.'&amp;p='.$this->ID.'#comments" title="'.T_('Moderate these feedbacks').'">'.get_icon( 'edit' ).' '.T_('Moderate...').'</a>';
1687              }
1688          }
1689          else
1690          { // User has no right to edit comments:
1691              $edit_comments_link = '';
1692          }
1693  
1694          // Inject Edit/moderate link as relevant:

1695          $zero = str_replace( '%s', $edit_comments_link, $zero );
1696          $one = str_replace( '%s', $edit_comments_link, $one );
1697          $more = str_replace( '%s', $edit_comments_link, $more );
1698  
1699          $r = $this->get_feedback_title( $type, $zero, $one, $more, 'draft' );
1700  
1701          if( !empty( $r ) )
1702          {
1703              echo $before.$r.$after;
1704          }
1705  
1706      }
1707  
1708  
1709  
1710      /**

1711       * Gets button for deleting the Item if user has proper rights

1712       *

1713       * @param string to display before link

1714       * @param string to display after link

1715       * @param string link text

1716       * @param string link title

1717       * @param string class name

1718       * @param boolean true to make this a button instead of a link

1719       * @param string page url for the delete action

1720       */
1721  	function get_delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#' )
1722      {
1723          global $current_User, $admin_url;
1724  
1725          if( ! is_logged_in() ) return false;
1726  
1727          if( ! $current_User->check_perm( 'blog_del_post', 'any', false, $this->blog_ID ) )
1728          { // User has right to delete this post
1729              return false;
1730          }
1731  
1732          if( $text == '#' )
1733          {
1734              if( ! $button )
1735              {
1736                  $text = get_icon( 'delete', 'imgtag' ).' '.T_('Delete!');
1737              }
1738              else
1739              {
1740                  $text = T_('Delete!');
1741              }
1742          }
1743  
1744          if( $title == '#' ) $title = T_('Delete this post');
1745  
1746          if( $actionurl == '#' )
1747          {
1748              $actionurl = $admin_url.'?ctrl=items&amp;action=delete&amp;post_ID=';
1749          }
1750          $url = $actionurl.$this->ID;
1751  
1752          $r = $before;
1753          if( $button )
1754          { // Display as button
1755              $r .= '<input type="button"';
1756              $r .= ' value="'.$text.'" title="'.$title.'" onclick="if ( confirm(\'';
1757              $r .= TS_('You are about to delete this post!\\nThis cannot be undone!');
1758              $r .= '\') ) { document.location.href=\''.$url.'\' }"';
1759              if( !empty( $class ) ) $r .= ' class="'.$class.'"';
1760              $r .= '/>';
1761          }
1762          else
1763          { // Display as link
1764              $r .= '<a href="'.$url.'" title="'.$title.'" onclick="return confirm(\'';
1765              $r .= TS_('You are about to delete this post!\\nThis cannot be undone!');
1766              $r .= '\')"';
1767              if( !empty( $class ) ) $r .= ' class="'.$class.'"';
1768              $r .= '>'.$text.'</a>';
1769          }
1770          $r .= $after;
1771  
1772          return $r;
1773      }
1774  
1775  
1776      /**

1777       * Displays button for deleting the Item if user has proper rights

1778       *

1779       * @param string to display before link

1780       * @param string to display after link

1781       * @param string link text

1782       * @param string link title

1783       * @param string class name

1784       * @param boolean true to make this a button instead of a link

1785       * @param string page url for the delete action

1786       */
1787  	function delete_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $button = false, $actionurl = '#' )
1788      {
1789          echo $this->get_delete_link( $before, $after, $text, $title, $class, $button, $actionurl );
1790      }
1791  
1792  
1793      /**

1794       * Provide link to edit a post if user has edit rights

1795       */
1796  	function get_edit_link( $params = array() )
1797      {
1798          global $current_User, $admin_url;
1799  
1800          if( ! is_logged_in() ) return false;
1801  
1802          if( ! $this->ID )
1803          { // preview..
1804              return false;
1805          }
1806  
1807          if( ! $current_User->check_perm( 'item_post!'.$this->status, 'edit', false, $this ) )
1808          { // User has no right to edit this post
1809              return false;
1810          }
1811  
1812          // Make sure we are not missing any param:

1813          $params = array_merge( array(
1814                  'before'       => ' ',
1815                  'after'        => ' ',
1816                  'text'         => '#',
1817                  'title'        => '#',
1818                  'class'        => '',
1819                  'save_context' => true,
1820              ), $params );
1821  
1822  
1823          if( $params['text'] == '#' ) $params['text'] = get_icon( 'edit' ).' '.T_('Edit...');
1824  
1825          if( $params['title'] == '#' ) $params['title'] = T_('Edit this post...');
1826  
1827          $actionurl = $admin_url.'?ctrl=items&amp;action=edit&amp;p='.$this->ID;
1828         if( $params['save_context'] )
1829          {
1830              $actionurl .= '&amp;redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) );
1831          }
1832  
1833  
1834          $r = $params['before'];
1835          $r .= '<a href="'.$actionurl;
1836          $r .= '" title="'.$params['title'].'"';
1837          if( !empty( $params['class'] ) ) $r .= ' class="'.$params['class'].'"';
1838          $r .=  '>'.$params['text'].'</a>';
1839          $r .= $params['after'];
1840  
1841          return $r;
1842      }
1843  
1844  
1845      /**

1846       * Template tag

1847       * @see Item::get_edit_link()

1848       */
1849  	function edit_link( $params = array() )
1850      {
1851          echo $this->get_edit_link( $params );
1852      }
1853  
1854  
1855      /**

1856       * Provide link to publish a post if user has edit rights

1857       *

1858       * Note: publishing date will be updated

1859       *

1860       * @param string to display before link

1861       * @param string to display after link

1862       * @param string link text

1863       * @param string link title

1864       * @param string class name

1865       * @param string glue between url params

1866       */
1867  	function get_publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $save_context = true )
1868      {
1869          global $current_User, $admin_url;
1870  
1871          if( ! is_logged_in() ) return false;
1872  
1873          if( ($this->status == 'published') // Already published!
1874              || ! ($current_User->check_perm( 'item_post!published', 'edit', false, $this ))
1875              || ! ($current_User->check_perm( 'edit_timestamp' ) ) )
1876          { // User has no right to publish this post now:
1877              return false;
1878          }
1879  
1880          if( $text == '#' ) $text = get_icon( 'publish', 'imgtag' ).' '.T_('Publish NOW!');
1881          if( $title == '#' ) $title = T_('Publish now using current date and time.');
1882  
1883          $r = $before;
1884          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=publish'.$glue.'post_ID='.$this->ID;
1885         if( $save_context )
1886          {
1887              $r .= $glue.'redirect_to='.rawurlencode( regenerate_url( '', '', '', '&' ) );
1888          }
1889          $r .= '" title="'.$title.'"';
1890          if( !empty( $class ) ) $r .= ' class="'.$class.'"';
1891          $r .= '>'.$text.'</a>';
1892          $r .= $after;
1893  
1894          return $r;
1895      }
1896  
1897  
1898  	function publish_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;', $save_context = true )
1899      {
1900          echo $this->get_publish_link( $before, $after, $text, $title, $class, $glue, $save_context );
1901      }
1902  
1903  
1904      /**

1905       * Provide link to deprecate a post if user has edit rights

1906       *

1907       * @param string to display before link

1908       * @param string to display after link

1909       * @param string link text

1910       * @param string link title

1911       * @param string class name

1912       * @param string glue between url params

1913       */
1914  	function get_deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;' )
1915      {
1916          global $current_User, $admin_url;
1917  
1918          if( ! is_logged_in() ) return false;
1919  
1920          if( ($this->status == 'deprecated') // Already deprecateded!
1921              || ! ($current_User->check_perm( 'item_post!deprecated', 'edit', false, $this )) )
1922          { // User has no right to deprecated this post:
1923              return false;
1924          }
1925  
1926          if( $text == '#' ) $text = get_icon( 'deprecate', 'imgtag' ).' '.T_('Deprecate!');
1927          if( $title == '#' ) $title = T_('Deprecate this post!');
1928  
1929          $r = $before;
1930          $r .= '<a href="'.$admin_url.'?ctrl=items'.$glue.'action=deprecate'.$glue.'post_ID='.$this->ID;
1931          $r .= '" title="'.$title.'"';
1932          if( !empty( $class ) ) $r .= ' class="'.$class.'"';
1933          $r .= '>'.$text.'</a>';
1934          $r .= $after;
1935  
1936          return $r;
1937      }
1938  
1939  
1940      /**

1941       * Display link to deprecate a post if user has edit rights

1942       *

1943       * @param string to display before link

1944       * @param string to display after link

1945       * @param string link text

1946       * @param string link title

1947       * @param string class name

1948       * @param string glue between url params

1949       */
1950  	function deprecate_link( $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '', $glue = '&amp;' )
1951      {
1952          echo $this->get_deprecate_link( $before, $after, $text, $title, $class, $glue );
1953      }
1954  
1955  
1956      /**

1957       * Template function: display priority of item

1958       *

1959       * @param string

1960       * @param string

1961       */
1962  	function priority( $before = '', $after = '' )
1963      {
1964          if( isset($this->priority) )
1965          {
1966              echo $before;
1967              echo $this->priority;
1968              echo $after;
1969          }
1970      }
1971  
1972  
1973      /**

1974       * Template function: display list of priority options

1975       */
1976  	function priority_options( $field_value, $allow_none )
1977      {
1978          $priority = isset($field_value) ? $field_value : $this->priority;
1979  
1980          $r = '';
1981          if( $allow_none )
1982          {
1983              $r = '<option value="">'./* TRANS: "None" select option */T_('No priority').'</option>';
1984          }
1985  
1986          foreach( $this->priorities as $i => $name )
1987          {
1988              $r .= '<option value="'.$i.'"';
1989              if( $priority == $i )
1990              {
1991                  $r .= ' selected="selected"';
1992              }
1993              $r .= '>'.$name.'</option>';
1994          }
1995  
1996          return $r;
1997      }
1998  
1999  
2000      /**

2001       * Template function: display checkable list of renderers

2002       *

2003       * @param array|NULL If given, assume these renderers to be checked.

2004       */
2005  	function renderer_checkboxes( $item_renderers = NULL )
2006      {
2007          global $Plugins, $inc_path, $admin_url;
2008  
2009          load_funcs('plugins/_plugin.funcs.php');
2010  
2011          $Plugins->restart(); // make sure iterator is at start position

2012  
2013          $atLeastOneRenderer = false;
2014  
2015          if( is_null($item_renderers) )
2016          {
2017              $item_renderers = $this->get_renderers();
2018          }
2019          // pre_dump( $item_renderers );

2020  
2021          echo '<input type="hidden" name="renderers_displayed" value="1" />';
2022  
2023          foreach( $Plugins->get_list_by_events( array('RenderItemAsHtml', 'RenderItemAsXml', 'RenderItemAsText') ) as $loop_RendererPlugin )
2024          { // Go through whole list of renders
2025              // echo ' ',$loop_RendererPlugin->code;

2026              if( empty($loop_RendererPlugin->code) )
2027              { // No unique code!
2028                  continue;
2029              }
2030              if( $loop_RendererPlugin->apply_rendering == 'stealth'
2031                  || $loop_RendererPlugin->apply_rendering == 'never' )
2032              { // This is not an option.
2033                  continue;
2034              }
2035              $atLeastOneRenderer = true;
2036  
2037              echo '<div>';
2038  
2039              // echo $loop_RendererPlugin->apply_rendering;

2040  
2041              echo '<input type="checkbox" class="checkbox" name="renderers[]" value="';
2042              echo $loop_RendererPlugin->code;
2043              echo '" id="renderer_';
2044              echo $loop_RendererPlugin->code;
2045              echo '"';
2046  
2047              switch( $loop_RendererPlugin->apply_rendering )
2048              {
2049                  case 'always':
2050                      echo ' checked="checked"';
2051                      echo ' disabled="disabled"';
2052                      break;
2053  
2054                  case 'opt-out':
2055                      if( in_array( $loop_RendererPlugin->code, $item_renderers ) // Option is activated
2056                          || in_array( 'default', $item_renderers ) ) // OR we're asking for default renderer set
2057                      {
2058                          echo ' checked="checked"';
2059                      }
2060                      break;
2061  
2062                  case 'opt-in':
2063                      if( in_array( $loop_RendererPlugin->code, $item_renderers ) ) // Option is activated
2064                      {
2065                          echo ' checked="checked"';
2066                      }
2067                      break;
2068  
2069                  case 'lazy':
2070                      if( in_array( $loop_RendererPlugin->code, $item_renderers ) ) // Option is activated
2071                      {
2072                          echo ' checked="checked"';
2073                      }
2074                      echo ' disabled="disabled"';
2075                      break;
2076              }
2077  
2078              echo ' title="';
2079              echo format_to_output($loop_RendererPlugin->short_desc, 'formvalue');
2080              echo '" />'
2081              .' <label for="renderer_';
2082              echo $loop_RendererPlugin->code;
2083              echo '" title="';
2084              echo format_to_output($loop_RendererPlugin->short_desc, 'formvalue');
2085              echo '">';
2086              echo format_to_output($loop_RendererPlugin->name);
2087              echo '</label>';
2088  
2089              // fp> TODO: the first thing we want here is a TINY javascript popup with the LONG desc. The links to readme and external help should be inside of the tiny popup.

2090              // fp> a javascript DHTML onhover help would be evenb better than the JS popup

2091  
2092              // internal README.html link:

2093              echo ' '.$loop_RendererPlugin->get_help_link('$readme');
2094              // external help link:

2095              echo ' '.$loop_RendererPlugin->get_help_link('$help_url');
2096  
2097              echo "</div>\n";
2098          }
2099  
2100          if( !$atLeastOneRenderer )
2101          {
2102              global $admin_url, $mode;
2103              echo '<a title="'.T_('Configure plugins').'" href="'.$admin_url.'?ctrl=plugins"'.'>'.T_('No renderer plugins are installed.').'</a>';
2104          }
2105      }
2106  
2107  
2108      /**

2109       * Template function: display status of item

2110       *

2111       * Statuses:

2112       * - published

2113       * - deprecated

2114       * - protected

2115       * - private

2116       * - draft

2117       *

2118       * @param string Output format, see {@link format_to_output()}

2119       */
2120  	function status( $params = array() )
2121      {
2122          global $post_statuses;
2123  
2124          // Make sure we are not missing any param:

2125          $params = array_merge( array(
2126                  'before'      => '',
2127                  'after'       => '',
2128                  'format'      => 'htmlbody',
2129              ), $params );
2130  
2131          echo $params['before'];
2132  
2133          if( $params['format'] == 'raw' )
2134          {
2135              status_raw();
2136          }
2137          else
2138          {
2139              echo format_to_output( $this->get('t_status'), $params['format'] );
2140          }
2141  
2142          echo $params['after'];
2143      }
2144  
2145  
2146  	function status_raw()
2147      {
2148          echo $this->status;
2149      }
2150  
2151  
2152      /**

2153       * Template function: display extra status of item

2154       *

2155       * @param string

2156       * @param string

2157       * @param string Output format, see {@link format_to_output()}

2158       */
2159  	function extra_status( $before = '', $after = '', $format = 'htmlbody' )
2160      {
2161          if( $format == 'raw' )
2162          {
2163              $this->disp( $this->get('t_extra_status'), 'raw' );
2164          }
2165          elseif( $extra_status = $this->get('t_extra_status') )
2166          {
2167              echo $before.format_to_output( $extra_status, $format ).$after;
2168          }
2169      }
2170  
2171  
2172  
2173       /**

2174       * Display tags for Item

2175       *

2176       * @param array of params

2177       * @param string Output format, see {@link format_to_output()}

2178       */
2179  	function tags( $params = array() )
2180      {
2181          $params = array_merge( array(
2182                  'before' =>           '<div>'.T_('Tags').': ',
2183                  'after' =>            '</div>',
2184                  'separator' =>        ', ',
2185                  'links' =>            true,
2186              ), $params );
2187  
2188          $tags = $this->get_tags();
2189  
2190          if( !empty( $tags ) )
2191          {
2192              echo $params['before'];
2193  
2194              if( $links = $params['links'] )
2195              {
2196                  $this->get_Blog();
2197                  $tag_view_url = url_add_param( $this->Blog->gen_blogurl(), 'tag=' );
2198              }
2199  
2200              $i = 0;
2201              foreach( $tags as $tag )
2202              {
2203                  if( $i++ > 0 )
2204                  {
2205                      echo $params['separator'];
2206                  }
2207                  if( $links )
2208                  {
2209                      echo '<a href="'.$tag_view_url.urlencode( $tag ).'">';
2210                  }
2211                  echo htmlspecialchars( $tag );
2212                  if( $links )
2213                  {
2214                      echo '</a>';
2215                  }
2216              }
2217  
2218              echo $params['after'];
2219          }
2220      }
2221  
2222  
2223      /**

2224       * Template function: Displays trackback autodiscovery information

2225       *

2226       * TODO: build into headers

2227       */
2228  	function trackback_rdf()
2229      {
2230          $this->get_Blog();
2231          if( ! $this->Blog->get( 'allowtrackbacks' ) )
2232          { // Trackbacks not allowed on this blog:
2233              return;
2234          }
2235  
2236          echo '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '."\n";
2237          echo '  xmlns:dc="http://purl.org/dc/elements/1.1/"'."\n";
2238          echo '  xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">'."\n";
2239          echo '<rdf:Description'."\n";
2240          echo '  rdf:about="';
2241          $this->permanent_url( 'single' );
2242          echo '"'."\n";
2243          echo '  dc:identifier="';
2244          $this->permanent_url( 'single' );
2245          echo '"'."\n";
2246          $this->title( '  dc:title="', '"'."\n", false, 'xmlattr' );
2247          echo '  trackback:ping="';
2248          $this->trackback_url();
2249          echo '" />'."\n";
2250          echo '</rdf:RDF>';
2251      }
2252  
2253  
2254      /**

2255       * Template function: displays url to use to trackback this item

2256       */
2257  	function trackback_url()
2258      {
2259          echo $this->get_trackback_url();
2260      }
2261  
2262  
2263      /**

2264       * Template function: get url to use to trackback this item

2265       * @return string

2266       */
2267  	function get_trackback_url()
2268      {
2269          global $htsrv_url, $Settings;
2270  
2271          // fp> TODO: get a clean (per blog) setting for this

2272          //    return $htsrv_url.'trackback.php/'.$this->ID;

2273  
2274          return $htsrv_url.'trackback.php?tb_id='.$this->ID;
2275      }
2276  
2277  
2278      /**

2279       * Template function: Display link to item related url

2280       *

2281       * @param string string to display before the link (if exists)

2282       * @param string string to display after the link (if exists)

2283       * @param string Link text to use (%s gets replaced by the URL).

2284       * @param array Attributes for the <a> tag (if the href attribute is set, %s gets replaced by the URL).

2285       * @param string Output format, see {@link format_to_output()}

2286       */
2287  	function url_link( $before = '', $after = '', $text = '%s', $attribs = array(), $format = 'htmlbody' )
2288      {
2289          if( ! empty( $this->url ) )
2290          {
2291              if( isset($attribs['href']) )
2292              {    // We have specified our own href attribute for the link:!
2293                  $attribs['href'] = str_replace( '%s', $this->url, $attribs['href'] );
2294              }
2295              else
2296              { // Use default href:
2297                  $attribs['href'] = $this->url;
2298              }
2299              echo $before;
2300              echo format_to_output( '<a'.get_field_attribs_as_string( $attribs ).'>'.str_replace( '%s', $this->url, $text ).'</a>', $format );
2301              echo $after;
2302          }
2303      }
2304  
2305  
2306      /**

2307       * Template function: Display the number of words in the post

2308       */
2309  	function wordcount()
2310      {
2311          echo (int)$this->wordcount; // may have been saved as NULL until 1.9

2312      }
2313  
2314  
2315      /**

2316       * Template function: Display the number of times the Item has been viewed

2317       *

2318       * Note: viewcount is incremented whenever the Item's content is displayed with "MORE"

2319       * (i-e full content), see {@link Item::content()}.

2320       *

2321       * Viewcount is NOT incremented on page reloads and other special cases, see {@link Hit::is_new_view()}

2322       *

2323       * %d gets replaced in all params by the number of views.

2324       *

2325       * @param string Link text to display when there are 0 views

2326       * @param string Link text to display when there is 1 views

2327       * @param string Link text to display when there are >1 views

2328       * @return string The phrase about the number of views.

2329       */
2330  	function get_views( $zero = '#', $one = '#', $more = '#' )
2331      {
2332          if( !$this->views )
2333          {
2334              $r = ( $zero == '#' ? T_( 'No views' ) : $zero );
2335          }
2336          elseif( $this->views == 1 )
2337          {
2338              $r = ( $one == '#' ? T_( '1 view' ) : $one );
2339          }
2340          else
2341          {
2342              $r = ( $more == '#' ? T_( '%d views' ) : $more );
2343          }
2344  
2345          return str_replace( '%d', $this->views, $r );
2346      }
2347  
2348  
2349      /**

2350       * Template function: Display a phrase about the number of Item views.

2351       *

2352       * @param string Link text to display when there are 0 views

2353       * @param string Link text to display when there is 1 views

2354       * @param string Link text to display when there are >1 views (include %d for # of views)

2355       * @return integer Number of views.

2356       */
2357  	function views( $zero = '#', $one = '#', $more = '#' )
2358      {
2359          echo $this->get_views( $zero, $one, $more );
2360  
2361          return $this->views;
2362      }
2363  
2364  
2365      /**

2366       * Set param value

2367       *

2368       * By default, all values will be considered strings

2369       *

2370       * @todo extra_cat_IDs recording

2371       *

2372       * @param string parameter name

2373       * @param mixed parameter value

2374       * @param boolean true to set to NULL if empty value

2375       * @return boolean true, if a value has been set; false if it has not changed

2376       */
2377  	function set( $parname, $parvalue, $make_null = false )
2378      {
2379          switch( $parname )
2380          {
2381              case 'pst_ID':
2382                  return $this->set_param( $parname, 'number', $parvalue, true );
2383  
2384              case 'content':
2385                  $r1 = $this->set_param( 'content', 'string', $parvalue, $make_null );
2386                  // Update wordcount as well:

2387                  $r2 = $this->set_param( 'wordcount', 'number', bpost_count_words($this->content), false );
2388                  return ( $r1 || $r2 ); // return true if one changed

2389  
2390              case 'wordcount':
2391                  return $this->set_param( 'wordcount', 'number', $parvalue, false );
2392  
2393              case 'datedeadline':
2394                  return $this->set_param( 'datedeadline', 'date', $parvalue, true );
2395  
2396              case 'renderers': // deprecated
2397                  return $this->set_renderers( $parvalue );
2398  
2399              default:
2400                  return parent::set( $parname, $parvalue, $make_null );
2401          }
2402      }
2403  
2404  
2405      /**

2406       * Set the renderers of the Item.

2407       *

2408       * @param array List of renderer codes.

2409       * @return boolean true, if it has been set; false if it has not changed

2410       */
2411  	function set_renderers( $renderers )
2412      {
2413          return $this->set_param( 'renderers', 'string', implode( '.', $renderers ) );
2414      }
2415  
2416  
2417      /**

2418       * Set the Author of the Item.

2419       *

2420       * @param User (Do NOT set to NULL or you may kill the current_User)

2421       * @return boolean true, if it has been set; false if it has not changed

2422       */
2423  	function set_creator_User( & $creator_User )
2424      {
2425          $this->creator_User = & $creator_User;
2426          $this->Author = & $this->creator_User; // deprecated  fp> TODO: Test and see if this line can be put once and for all in the constructor

2427          return $this->set( $this->creator_field, $creator_User->ID );
2428      }
2429  
2430  
2431      /**

2432       * Create a new Item/Post and insert it into the DB

2433       *

2434       * This function has to handle all needed DB dependencies!

2435       *

2436       * @deprecated since EVO_NEXT_VERSION. Use set() + dbinsert() instead

2437       */
2438  	function insert(
2439          $author_user_ID,              // Author
2440          $post_title,
2441          $post_content,
2442          $post_timestamp,              // 'Y-m-d H:i:s'
2443          $main_cat_ID = 1,             // Main cat ID
2444          $extra_cat_IDs = array(),     // Table of extra cats
2445          $post_status = 'published',
2446          $post_locale = '#',
2447          $post_urltitle = '',
2448          $post_url = '',
2449          $post_comment_status = 'open',
2450          $post_renderers = array('default'),
2451          $item_typ_ID = 1,
2452          $item_st_ID = NULL )
2453      {
2454          global $DB, $query, $UserCache;
2455          global $localtimenow, $default_locale;
2456  
2457          if( $post_locale == '#' ) $post_locale = $default_locale;
2458  
2459          // echo 'INSERTING NEW POST ';

2460  
2461          if( isset( $UserCache ) )    // DIRTY HACK
2462          { // If not in install procedure...
2463              $this->set_creator_User( $UserCache->get_by_ID( $author_user_ID ) );
2464          }
2465          else
2466          {
2467              $this->set( $this->creator_field, $author_user_ID );
2468          }
2469          $this->set( $this->lasteditor_field, $this->{$this->creator_field} );
2470          $this->set( 'title', $post_title );
2471          $this->set( 'urltitle', $post_urltitle );
2472          $this->set( 'content', $post_content );
2473          $this->set( 'datestart', $post_timestamp );
2474  
2475          // TODO: dh> $localtimenow is not defined during install! - all sample posts get a last-modified date of 1970-01-01

2476          $this->set( 'datemodified', date('Y-m-d H:i:s',$localtimenow) );
2477  
2478          $this->set( 'main_cat_ID', $main_cat_ID );
2479          $this->set( 'extra_cat_IDs', $extra_cat_IDs );
2480          $this->set( 'status', $post_status );
2481          $this->set( 'locale', $post_locale );
2482          $this->set( 'url', $post_url );
2483          $this->set( 'comment_status', $post_comment_status );
2484          $this->set_renderers( $post_renderers );
2485          $this->set( 'ptyp_ID', $item_typ_ID );
2486          $this->set( 'pst_ID', $item_st_ID );
2487  
2488          // INSERT INTO DB:

2489          $this->dbinsert();
2490  
2491          return $this->ID;
2492      }
2493  
2494  
2495      /**

2496       * Insert object into DB based on previously recorded changes

2497       *

2498       * @return boolean true on success

2499       */
2500  	function dbinsert()
2501      {
2502          global $DB, $current_User, $Plugins;
2503  
2504          $DB->begin();
2505  
2506          if( empty($this->creator_user_ID) )
2507          { // No creator assigned yet, use current user:
2508              $this->set_creator_User( $current_User );
2509          }
2510  
2511          // validate url title

2512          $this->set( 'urltitle', urltitle_validate( $this->urltitle, $this->title, 0, false, $this->dbprefix, $this->dbIDname, $this->dbtablename) );
2513  
2514          $this->update_renderers_from_Plugins();
2515  
2516          // TODO: allow a plugin to cancel update here (by returning false)?

2517          $Plugins->trigger_event( 'PrependItemInsertTransact', $params = array( 'Item' => & $this ) );
2518  
2519          $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook

2520  
2521          if( $result = parent::dbinsert() )
2522          { // We could insert the item object..
2523  
2524              // Let's handle the extracats:

2525              $this->insert_update_extracats( 'insert' );
2526  
2527              // Let's handle the tags:

2528              $this->insert_update_tags( 'insert' );
2529  
2530              $DB->commit();
2531  
2532              $Plugins->trigger_event( 'AfterItemInsert', $params = array( 'Item' => & $this, 'dbchanges' => $dbchanges ) );
2533          }
2534          else
2535          {
2536              $DB->rollback();
2537          }
2538  
2539          return $result;
2540      }
2541  
2542  
2543  
2544  
2545      /**

2546       * Update the DB based on previously recorded changes

2547       *

2548       * @return boolean true on success

2549       */
2550  	function dbupdate()
2551      {
2552          global $DB, $Plugins;
2553  
2554          $DB->begin();
2555  
2556          // validate url title

2557          if( empty($this->urltitle) || isset($this->dbchanges['post_urltitle']) )
2558          { // Url title has changed or is empty
2559              // echo 'updating url title';

2560              $this->set( 'urltitle', urltitle_validate( $this->urltitle, $this->title, $this->ID,
2561                                                                  false, $this->dbprefix, $this->dbIDname, $this->dbtablename ) );
2562          }
2563  
2564          $this->update_renderers_from_Plugins();
2565  
2566          // TODO: dh> allow a plugin to cancel update here (by returning false)?

2567          $Plugins->trigger_event( 'PrependItemUpdateTransact', $params = array( 'Item' => & $this ) );
2568  
2569          $dbchanges = $this->dbchanges; // we'll save this for passing it to the plugin hook

2570  
2571          if( $result = parent::dbupdate() )
2572          { // We could update the item object..
2573  
2574              // Let's handle the extracats:

2575              $this->insert_update_extracats( 'update' );
2576  
2577              // Let's handle the extracats:

2578              $this->insert_update_tags( 'update' );
2579  
2580              // Empty pre-rendered content cache - any item property may have influence on it:

2581              $DB->query( 'DELETE FROM T_items__prerendering WHERE itpr_itm_ID = '.$this->ID );
2582              $this->content_prerendered = NULL;
2583  
2584              $DB->commit();
2585  
2586              $Plugins->trigger_event( 'AfterItemUpdate', $params = array( 'Item' => & $this, 'dbchanges' => $dbchanges ) );
2587          }
2588          else
2589          {
2590              $DB->commit();
2591          }
2592  
2593          return $result;
2594      }
2595  
2596  
2597      /**

2598       * Trigger event AfterItemDelete after calling parent method.

2599       *

2600       * @todo fp> delete related stuff: comments, cats, file links...

2601       *

2602       * @return boolean true on success

2603       */
2604  	function dbdelete()
2605      {
2606          global $DB, $Plugins;
2607  
2608          // remember ID, because parent method resets it to 0

2609          $old_ID = $this->ID;
2610  
2611          $DB->begin();
2612  
2613          if( $r = parent::dbdelete() )
2614          {
2615              // Empty pre-rendered content cache:

2616              $DB->query( 'DELETE FROM T_items__prerendering
2617                                 WHERE itpr_itm_ID = '.$this->ID );
2618              $this->content_prerendered = NULL;
2619  
2620              $DB->commit();
2621  
2622              // re-set the ID for the Plugin event

2623              $this->ID = $old_ID;
2624  
2625              $Plugins->trigger_event( 'AfterItemDelete', $params = array( 'Item' => & $this ) );
2626  
2627              $this->ID = 0;
2628          }
2629          else
2630          {
2631              $DB->rollback();
2632          }
2633  
2634          return $r;
2635      }
2636  
2637  
2638      /**

2639       * @param string 'insert' | 'update'

2640       */
2641  	function insert_update_extracats( $mode )
2642      {
2643          global $DB;
2644  
2645          $DB->begin();
2646  
2647          if( ! is_null( $this->extra_cat_IDs ) )
2648          { // Okay the extra cats are defined:
2649  
2650              if( $mode == 'update' )
2651              {
2652                  // delete previous extracats:

2653                  $DB->query( 'DELETE FROM T_postcats WHERE postcat_post_ID = '.$this->ID, 'delete previous extracats' );
2654              }
2655  
2656              // insert new extracats:

2657              $query = "INSERT INTO T_postcats( postcat_post_ID, postcat_cat_ID ) VALUES ";
2658              foreach( $this->extra_cat_IDs as $extra_cat_ID )
2659              {
2660                  //echo "extracat: $extracat_ID <br />";

2661                  $query .= "( $this->ID, $extra_cat_ID ),";
2662              }
2663              $query = substr( $query, 0, strlen( $query ) - 1 );
2664              $DB->query( $query, 'insert new extracats' );
2665          }
2666  
2667          $DB->commit();
2668      }
2669  
2670  
2671      /**

2672       * Save tags to DB

2673       *

2674       * @param string 'insert' | 'update'

2675       */
2676  	function insert_update_tags( $mode )
2677      {
2678          global $DB;
2679  
2680          if( isset( $this->tags ) )
2681          { // Okay the tags are defined:
2682  
2683              $DB->begin();
2684  
2685              if( $mode == 'update' )
2686              {    // delete previous tag associations:
2687                  // Note: actual tags never get deleted

2688                  $DB->query( 'DELETE FROM T_items__itemtag
2689                                              WHERE itag_itm_ID = '.$this->ID, 'delete previous tags' );
2690              }
2691  
2692              if( !empty($this->tags) )
2693              {
2694                  // Find the tags that are already in the DB

2695                  $query = 'SELECT tag_name
2696                                          FROM T_items__tag
2697                                       WHERE tag_name IN ('.$DB->quote($this->tags).')';
2698                  $existing_tags = $DB->get_col( $query, 0, 'Find existing tags' );
2699  
2700                  $new_tags = array_diff( $this->tags, $existing_tags );
2701                  //pre_dump($new_tags);

2702  
2703                  if( !empty( $new_tags ) )
2704                  {    // insert new tags:
2705                      $query = "INSERT INTO T_items__tag( tag_name ) VALUES ";
2706                      foreach( $new_tags as $tag )
2707                      {
2708                          $query .= '( '.$DB->quote($tag).' ),';
2709                      }
2710                      $query = substr( $query, 0, strlen( $query ) - 1 );
2711                      $DB->query( $query, 'insert new tags' );
2712                  }
2713  
2714                  // ASSOC:

2715                  $query = 'INSERT INTO T_items__itemtag( itag_itm_ID, itag_tag_ID )
2716                                    SELECT '.$this->ID.', tag_ID
2717                                        FROM T_items__tag
2718                                       WHERE tag_name IN ('.$DB->quote($this->tags).')';
2719                  $DB->query( $query, 'Make tag associations!' );
2720              }
2721  
2722              $DB->commit();
2723          }
2724      }
2725  
2726  
2727      /**

2728       * Increment the view count of the item directly in DB (if the item's Author is not $current_User).

2729       *

2730       * This method serves TWO purposes (that would break if we used dbupdate() ) :

2731       *  - Increment the viewcount WITHOUT affecting the lastmodified date and user.

2732       *  - Increment the viewcount in an ATOMIC manner (even if several hits on the same Item occur simultaneously).

2733       *

2734       * This also triggers the plugin event 'ItemViewsIncreased' if the view count has been increased.

2735       *

2736       * @return boolean Did we increase view count?

2737       */
2738  	function inc_viewcount()
2739      {
2740          global $Plugins, $DB, $current_User, $Debuglog;
2741  
2742          if( isset( $current_User ) && ( $current_User->ID == $this->creator_user_ID ) )
2743          {
2744              $Debuglog->add( 'Not incrementing view count, because viewing user is creator of the item.', 'items' );
2745  
2746              return false;
2747          }
2748  
2749          $DB->query( 'UPDATE T_items__item
2750                          SET post_views = post_views + 1
2751                        WHERE '.$this->dbIDname.' = '.$this->ID );
2752  
2753          // Trigger event that the item's view has been increased

2754          $Plugins->trigger_event( 'ItemViewsIncreased', array( 'Item' => & $this ) );
2755  
2756          return true;
2757      }
2758  
2759  
2760      /**

2761       * Get the User who is assigned to the Item.

2762       *

2763       * @return User|NULL NULL if no user is assigned.

2764       */
2765  	function get_assigned_User()
2766      {
2767          if( ! isset($this->assigned_User) && isset($this->assigned_user_ID) )
2768          {
2769              $UserCache = & get_Cache( 'UserCache' );
2770              $this->assigned_User = & $UserCache->get_by_ID( $this->assigned_user_ID );
2771          }
2772  
2773          return $this->assigned_User;
2774      }
2775  
2776  
2777      /**

2778       * Get the User who created the Item.

2779       *

2780       * @return User

2781       */
2782      function & get_creator_User()
2783      {
2784          if( is_null($this->creator_User) )
2785          {
2786              $UserCache = & get_Cache( 'UserCache' );
2787              $this->creator_User = & $UserCache->get_by_ID( $this->creator_user_ID );
2788              $this->Author = & $this->creator_User;  // deprecated

2789          }
2790  
2791          return $this->creator_User;
2792      }
2793  
2794  
2795      /**

2796       * Get the Blog object for the Item.

2797       *

2798       * @return Blog

2799       */
2800      function & get_Blog()
2801      {
2802          if( is_null($this->Blog) )
2803          {
2804              $this->load_Blog();
2805          }
2806  
2807          return $this->Blog;
2808      }
2809  
2810  
2811      /**

2812       * Load the Blog object for the Item, without returning it.

2813       *

2814       * This is needed for {@link Results} object callbacks.

2815       */
2816  	function load_Blog()
2817      {
2818          if( is_null($this->Blog) )
2819          {
2820              $BlogCache = & get_Cache( 'BlogCache' );
2821              $this->Blog = & $BlogCache->get_by_ID( $this->blog_ID );
2822          }
2823      }
2824  
2825  
2826      /**

2827       * Execute or schedule post(=after) processing tasks

2828       *

2829       * Includes notifications & pings

2830       */
2831  	function handle_post_processing()
2832      {
2833          global $Settings, $Messages;
2834  
2835          $notifications_mode = $Settings->get('outbound_notifications_mode');
2836  
2837          if( $notifications_mode == 'off' )
2838          {    // Exit silently
2839              return false;
2840          }
2841  
2842          if( $this->notifications_status == 'finished' )
2843          { // pings have been done before
2844              $Messages->add( T_('Post had already pinged: skipping notifications...'), 'note' );
2845              return false;
2846          }
2847  
2848          if( $this->notifications_status != 'noreq' )
2849          { // pings have been done before
2850  
2851              // TODO: Check if issue_date has changed and reschedule

2852  
2853              $Messages->add( T_('Post processing already pending...'), 'note' );
2854              return false;
2855          }
2856  
2857          if( $this->status != 'published' )
2858          {
2859  
2860              // TODO: discard any notification that may be pending!

2861  
2862              $Messages->add( T_('Post not publicly published: skipping notifications...'), 'note' );
2863              return false;
2864          }
2865  
2866          if( $notifications_mode == 'immediate' )
2867          {    // We want to do the post processing immediately:
2868              // send outbound pings:

2869              $this->send_outbound_pings();
2870  
2871              // Send email notifications now!

2872              $this->send_email_notifications( false );
2873  
2874              // Record that processing has been done:

2875              $this->set( 'notifications_status', 'finished' );
2876          }
2877          else
2878          {    // We want asynchronous post processing:
2879              $Messages->add( T_('Scheduling asynchronous notifications...'), 'note' );
2880  
2881              // CREATE OBJECT:

2882              load_class( '/cron/model/_cronjob.class.php' );
2883              $edited_Cronjob = & new Cronjob();
2884  
2885              // start datetime. We do not want to ping before the post is effectively published:

2886              $edited_Cronjob->set( 'start_datetime', $this->issue_date );
2887  
2888              // no repeat.

2889  
2890              // name:

2891              $edited_Cronjob->set( 'name', sprintf( T_('Send notifications for &laquo;%s&raquo;'), strip_tags($this->title) ) );
2892  
2893              // controller:

2894              $edited_Cronjob->set( 'controller', 'cron/jobs/_post_notifications.job.php' );
2895  
2896              // params: specify which post this job is supposed to send notifications for:

2897              $edited_Cronjob->set( 'params', array( 'item_ID' => $this->ID ) );
2898  
2899              // Save cronjob to DB:

2900              $edited_Cronjob->dbinsert();
2901  
2902              // Memorize the cron job ID which is going to handle this post:

2903              $this->set( 'notifications_ctsk_ID', $edited_Cronjob->ID );
2904  
2905              // Record that processing has been scheduled:

2906              $this->set( 'notifications_status', 'todo' );
2907          }
2908  
2909          // Save the new processing status to DB

2910          $this->dbupdate();
2911  
2912          return true;
2913      }
2914  
2915  
2916      /**

2917       * Send email notifications to subscribed users

2918       *

2919       * @todo fp>> shall we notify suscribers of blog were this is in extra-cat? blueyed>> IMHO yes.

2920       */
2921  	function send_email_notifications( $display = true )
2922      {
2923          global $DB, $admin_url, $debug, $Debuglog;
2924  
2925           $edited_Blog = & $this->get_Blog();
2926  
2927          if( ! $edited_Blog->get_setting( 'allow_subscriptions' ) )
2928          {    // Subscriptions not enabled!
2929              return;
2930          }
2931  
2932          if( $display )
2933          {
2934              echo "<div class=\"panelinfo\">\n";
2935              echo '<h3>', T_('Notifying subscribed users...'), "</h3>\n";
2936          }
2937  
2938          // Get list of users who want to be notfied:

2939          // TODO: also use extra cats/blogs??

2940          $sql = 'SELECT DISTINCT user_email, user_locale
2941                              FROM T_subscriptions INNER JOIN T_users ON sub_user_ID = user_ID
2942                          WHERE sub_coll_ID = '.$this->blog_ID.'
2943                              AND sub_items <> 0
2944                              AND LENGTH(TRIM(user_email)) > 0';
2945          $notify_list = $DB->get_results( $sql );
2946  
2947          // Preprocess list: (this comes form Comment::send_email_notifications() )

2948          $notify_array = array();
2949          foreach( $notify_list as $notification )
2950          {
2951              $notify_array[$notification->user_email] = $notification->user_locale;
2952          }
2953  
2954          if( empty($notify_array) )
2955          { // No-one to notify:
2956              if( $display )
2957              {
2958                  echo '<p>', T_('No-one to notify.'), "</p>\n</div>\n";
2959              }
2960              return false;
2961          }
2962  
2963          /*

2964           * We have a list of email addresses to notify:

2965           */
2966          $this->get_creator_User();
2967          $mail_from = '"'.$this->creator_User->get('preferredname').'" <'.$this->creator_User->get('email').'>';
2968  
2969          // Send emails:

2970          $cache_by_locale = array();
2971          foreach( $notify_array as $notify_email => $notify_locale )
2972          {
2973              if( ! isset($cache_by_locale[$notify_locale]) )
2974              { // No message for this locale generated yet:
2975                  locale_temp_switch($notify_locale);
2976  
2977                  // Calculate length for str_pad to align labels:

2978                  $pad_len = max( strlen(T_('Blog')), strlen(T_('Author')), strlen(T_('Title')), strlen(T_('Url')), strlen(T_('Content')) );
2979  
2980                  $cache_by_locale[$notify_locale]['subject'] = sprintf( T_('[%s] New post: "%s"'), $edited_Blog->get('shortname'), $this->get('title') );
2981  
2982                  $cache_by_locale[$notify_locale]['message'] =
2983                      str_pad( T_('Blog'), $pad_len ).': '.$edited_Blog->get('shortname')
2984                      .' ( '.str_replace('&amp;', '&', $edited_Blog->gen_blogurl())." )\n"
2985  
2986                      .str_pad( T_('Author'), $pad_len ).': '.$this->creator_User->get('preferredname').' ('.$this->creator_User->get('login').")\n"
2987  
2988                      .str_pad( T_('Title'), $pad_len ).': '.$this->get('title')."\n"
2989  
2990                      // linked URL or "-" if empty:

2991                      .str_pad( T_('Url'), $pad_len ).': '.( empty( $this->url ) ? '-' : str_replace('&amp;', '&', $this->get('url')) )."\n"
2992  
2993                      .str_pad( T_('Content'), $pad_len ).': '
2994                          // TODO: We MAY want to force a short URL and avoid it to wrap on a new line in the mail which may prevent people from clicking

2995                          // TODO: might get moved onto a single line, at the end of the content..

2996                          .str_replace('&amp;', '&', $this->get_permanent_url())."\n\n"
2997  
2998                      .$this->get('content')."\n"
2999  
3000                      // Footer:

3001                      ."\n-- \n"
3002                      .T_('Edit/Delete').': '.$admin_url.'?ctrl=items&blog='.$this->blog_ID.'&p='.$this->ID."\n\n"
3003  
3004                      .T_('Edit your subscriptions/notifications').': '.str_replace('&amp;', '&', url_add_param( $edited_Blog->gen_blogurl(), 'disp=subs' ) )."\n";
3005  
3006                  locale_restore_previous();
3007              }
3008  
3009              if( $display ) echo T_('Notifying:').$notify_email."<br />\n";
3010              if( $debug >= 2 )
3011              {
3012                  echo "<p>Sending notification to $notify_email:<pre>$cache_by_locale[$notify_locale]['message']</pre>";
3013              }
3014  
3015              send_mail( $notify_email, $cache_by_locale[$notify_locale]['subject'], $cache_by_locale[$notify_locale]['message'], $mail_from );
3016          }
3017  
3018          if( $display ) echo '<p>', T_('Done.'), "</p>\n</div>\n";
3019      }
3020  
3021  
3022    /**

3023       * Send outbound pings for a post

3024       */
3025  	function send_outbound_pings()
3026      {
3027          global $Plugins, $baseurl, $Messages;
3028  
3029          load_funcs('_ext/xmlrpc/_xmlrpc.php' );
3030  
3031          $this->load_Blog();
3032          $ping_plugins = array_unique(explode(',', $this->Blog->get_setting('ping_plugins')));
3033  
3034          if( preg_match( '#^http://localhost[/:]#', $baseurl)
3035              || preg_match( '~^\w+://[^/]+\.local/~', $baseurl ) /* domain ending in ".local" */  )
3036          {
3037              $Messages->add( T_('Skipping pings (Running on localhost).'), 'note' );
3038          }
3039          else foreach( $ping_plugins as $plugin_code )
3040          {
3041              $Plugin = & $Plugins->get_by_code($plugin_code);
3042  
3043              if( $Plugin )
3044              {
3045                  $Messages->add( sprintf(T_('Pinging %s...'), $Plugin->ping_service_name), 'note' );
3046                  $params = array( 'Item' => & $this, 'xmlrpcresp' => NULL, 'display' => false );
3047  
3048                  $r = $Plugin->ItemSendPing( $params );
3049  
3050                  if( isset($params['xmlrpcresp']) && is_a($params['xmlrpcresp'], 'xmlrpcresp') )
3051                  {
3052                      // dh> TODO: let xmlrpc_displayresult() handle $Messages (e.g. "error", but should be connected/after the "Pinging %s..." from above)

3053                      ob_start();
3054                      xmlrpc_displayresult( $params['xmlrpcresp'], true );
3055                      $Messages->add( ob_get_contents(), 'note' );
3056                      ob_end_clean();
3057                  }
3058              }
3059          }
3060      }
3061  
3062  
3063      /**

3064       * Get a member param by its name

3065       *

3066       * @param mixed Name of parameter

3067       * @return mixed Value of parameter

3068       */
3069  	function get( $parname )
3070      {
3071          global $post_statuses;
3072  
3073          switch( $parname )
3074          {
3075              case 't_author':
3076                  // Text: author

3077                  $this->get_creator_User();
3078                  return $this->creator_User->get( 'preferredname' );
3079  
3080              case 't_assigned_to':
3081                  // Text: assignee

3082                  if( ! $this->get_assigned_User() )
3083                  {
3084                      return '';
3085                  }
3086                  return $this->assigned_User->get( 'preferredname' );
3087  
3088              case 't_status':
3089                  // Text status:

3090                  return T_( $post_statuses[$this->status] );
3091  
3092              case 't_extra_status':
3093                  $ItemStatusCache = & get_Cache( 'ItemStatusCache' );
3094                  if( ! ($Element = & $ItemStatusCache->get_by_ID( $this->pst_ID, true, false ) ) )
3095                  { // No status:
3096                      return '';
3097                  }
3098                  return $Element->get_name();
3099  
3100              case 't_type':
3101                  // Item type (name):

3102                  if( empty($this->ptyp_ID) )
3103                  {
3104                      return '';
3105                  }
3106  
3107                  $ItemTypeCache = & get_Cache( 'ItemTypeCache' );
3108                  $type_Element = & $ItemTypeCache->get_by_ID( $this->ptyp_ID );
3109                  return $type_Element->get_name();
3110  
3111              case 't_priority':
3112                  return $this->priorities[ $this->priority ];
3113  
3114              case 'pingsdone':
3115                  // Deprecated by fp 2006-08-21

3116                  return ($this->post_notifications_status == 'finished');
3117          }
3118  
3119          return parent::get( $parname );
3120      }
3121  
3122  
3123      /**

3124       * Assign the item to the first category we find in the requested collection

3125       *

3126       * @param integer $collection_ID

3127       */
3128  	function assign_to_first_cat_for_collection( $collection_ID )
3129      {
3130          global $DB;
3131  
3132          // Get the first category ID for the collection ID param

3133          $cat_ID = $DB->get_var( '
3134                  SELECT cat_ID
3135                      FROM T_categories
3136                   WHERE cat_blog_ID = '.$collection_ID.'
3137                   ORDER BY cat_ID ASC
3138                   LIMIT 1' );
3139  
3140          // Set to the item the first category we got

3141          $this->set( 'main_cat_ID', $cat_ID );
3142      }
3143  
3144  
3145      /**

3146       * Get the list of renderers for this Item.

3147       * @return array

3148       */
3149  	function get_renderers()
3150      {
3151          return explode( '.', $this->renderers );
3152      }
3153  
3154  
3155      /**

3156       * Get the list of validated renderers for this Item. This includes stealth plugins etc.

3157       * @return array List of validated renderer codes

3158       */
3159  	function get_renderers_validated()
3160      {
3161          if( ! isset($this->renderers_validated) )
3162          {
3163              $Plugins_admin = & get_Cache('Plugins_admin');
3164              $this->renderers_validated = $Plugins_admin->validate_renderer_list( $this->get_renderers() );
3165          }
3166          return $this->renderers_validated;
3167      }
3168  
3169  
3170      /**

3171       * Add a renderer (by code) to the Item.

3172       * @param string Renderer code to add for this item

3173       * @return boolean True if renderers have changed

3174       */
3175  	function add_renderer( $renderer_code )
3176      {
3177          $renderers = $this->get_renderers();
3178          if( in_array( $renderer_code, $renderers ) )
3179          {
3180              return false;
3181          }
3182  
3183          $renderers[] = $renderer_code;
3184          $this->set_renderers( $renderers );
3185  
3186          $this->renderers_validated = NULL;
3187          //echo 'Added renderer '.$renderer_code;

3188      }
3189  
3190  
3191      /**

3192       * Remove a renderer (by code) from the Item.

3193       * @param string Renderer code to remove for this item

3194       * @return boolean True if renderers have changed

3195       */
3196  	function remove_renderer( $renderer_code )
3197      {
3198          $r = false;
3199          $renderers = $this->get_renderers();
3200          while( ( $key = array_search( $renderer_code, $renderers ) ) !== false )
3201          {
3202              $r = true;
3203              unset($renderers[$key]);
3204          }
3205  
3206          if( $r )
3207          {
3208              $this->set_renderers( $renderers );
3209              $this->renderers_validated = NULL;
3210              //echo 'Removed renderer '.$renderer_code;

3211          }
3212          return $r;
3213      }
3214  }
3215  
3216  
3217  /*

3218   * $Log: _item.class.php,v $

3219   * Revision 1.15  2007/11/04 01:10:57  fplanque

3220   * skin cleanup continued

3221   *

3222   * Revision 1.14  2007/11/03 23:54:38  fplanque

3223   * skin cleanup continued

3224   *

3225   * Revision 1.13  2007/11/03 21:04:26  fplanque

3226   * skin cleanup

3227   *

3228   * Revision 1.12  2007/11/02 01:54:46  fplanque

3229   * comment ratings

3230   *

3231   * Revision 1.11  2007/09/13 19:16:14  fplanque

3232   * feedback_link() cleanup

3233   *

3234   * Revision 1.10  2007/09/13 02:37:22  fplanque

3235   * special cases

3236   *

3237   * Revision 1.9  2007/09/11 23:10:39  fplanque

3238   * translation updates

3239   *

3240   * Revision 1.8  2007/09/10 14:53:04  fplanque

3241   * cron fix

3242   *

3243   * Revision 1.7  2007/09/09 12:51:58  fplanque

3244   * cleanup

3245   *

3246   * Revision 1.6  2007/09/09 09:15:59  yabs

3247   * validation

3248   *

3249   * Revision 1.5  2007/09/08 19:31:28  fplanque

3250   * cleanup of XML feeds for comments on individual posts.

3251   *

3252   * Revision 1.4  2007/09/04 22:16:33  fplanque

3253   * in context editing of posts

3254   *

3255   * Revision 1.3  2007/08/28 02:43:40  waltercruz

3256   * Template function to get the rss link to the feeds of the comments on each post

3257   *

3258   * Revision 1.2  2007/07/03 23:21:32  blueyed

3259   * Fixed includes/requires in/for tests

3260   *

3261   * Revision 1.1  2007/06/25 11:00:24  fplanque

3262   * MODULES (refactored MVC)

3263   *

3264   * Revision 1.184  2007/06/24 22:26:34  fplanque

3265   * improved feedback template

3266   *

3267   * Revision 1.183  2007/06/21 00:44:37  fplanque

3268   * linkblog now a widget

3269   *

3270   * Revision 1.182  2007/06/20 21:42:13  fplanque

3271   * implemented working widget/plugin params

3272   *

3273   * Revision 1.180  2007/06/18 20:59:55  fplanque

3274   * do not display link to comments if comments are disabled

3275   *

3276   * Revision 1.179  2007/06/13 23:29:02  fplanque

3277   * minor

3278   *

3279   * Revision 1.178  2007/06/11 01:55:57  fplanque

3280   * level based user permissions

3281   *

3282   * Revision 1.177  2007/05/28 15:18:30  fplanque

3283   * cleanup

3284   *

3285   * Revision 1.176  2007/05/28 01:33:22  fplanque

3286   * permissions/fixes

3287   *

3288   * Revision 1.175  2007/05/27 00:35:26  fplanque

3289   * tag display + tag filtering

3290   *

3291   * Revision 1.174  2007/05/20 01:01:35  fplanque

3292   * make trackback silent when it should be

3293   *

3294   * Revision 1.173  2007/05/14 02:47:23  fplanque

3295   * (not so) basic Tags framework

3296   *

3297   * Revision 1.172  2007/05/13 22:02:07  fplanque

3298   * removed bloated $object_def

3299   *

3300   * Revision 1.171  2007/04/26 00:11:11  fplanque

3301   * (c) 2007

3302   *

3303   * Revision 1.170  2007/04/15 13:34:36  blueyed

3304   * Fixed default $url generation in page_links()

3305   *

3306   * Revision 1.169  2007/04/05 22:57:33  fplanque

3307   * Added hook: UnfilterItemContents

3308   *

3309   * Revision 1.168  2007/03/31 22:46:46  fplanque

3310   * FilterItemContent event

3311   *

3312   * Revision 1.167  2007/03/26 12:59:18  fplanque

3313   * basic pages support

3314   *

3315   * Revision 1.166  2007/03/25 10:19:30  fplanque

3316   * cleanup

3317   *

3318   * Revision 1.165  2007/03/24 20:41:16  fplanque

3319   * Refactored a lot of the link junk.

3320   * Made options blog specific.

3321   * Some junk still needs to be cleaned out. Will do asap.

3322   *

3323   * Revision 1.164  2007/03/19 23:59:32  fplanque

3324   * minor

3325   *

3326   * Revision 1.163  2007/03/18 03:43:19  fplanque

3327   * EXPERIMENTAL

3328   * Splitting Item/ItemLight and ItemList/ItemListLight

3329   * Goal: Handle Items with less footprint than with their full content

3330   * (will be even worse with multiple languages/revisions per Item)

3331   *

3332   * Revision 1.162  2007/03/11 23:57:07  fplanque

3333   * item editing: allow setting to 'redirected' status

3334   *

3335   * Revision 1.161  2007/03/06 12:18:08  fplanque

3336   * got rid of dirty Item::content()

3337   * Advantage: the more link is now independant. it can be put werever people want it

3338   *

3339   * Revision 1.160  2007/03/05 04:52:42  fplanque

3340   * better precision for viewcounts

3341   *

3342   * Revision 1.159  2007/03/05 04:49:17  fplanque

3343   * better precision for viewcounts

3344   *

3345   * Revision 1.158  2007/03/05 02:13:26  fplanque

3346   * improved dashboard

3347   *

3348   * Revision 1.157  2007/03/05 01:47:50  fplanque

3349   * splitting up Item::content() - proof of concept.

3350   * needs to be optimized.

3351   *

3352   * Revision 1.156  2007/03/03 01:14:11  fplanque

3353   * new methods for navigating through posts in single item display mode

3354   *

3355   * Revision 1.155  2007/03/02 04:40:38  fplanque

3356   * fixed/commented a lot of stuff with the feeds

3357   *

3358   * Revision 1.154  2007/03/02 03:09:12  fplanque

3359   * rss length doesn't make sense since it doesn't apply to html format anyway.

3360   * clean solutionwould be to handle an "excerpt" field separately

3361   *

3362   * Revision 1.153  2007/02/23 19:16:07  blueyed

3363   * MFB: Fixed handling of Item::content for pre-rendering (it gets passed by reference!)

3364   *

3365   * Revision 1.152  2007/02/18 22:51:26  waltercruz

3366   * Fixing a little confusion with quotes and string concatenation

3367   *

3368   * Revision 1.151  2007/02/08 03:45:40  waltercruz

3369   * Changing double quotes to single quotes

3370   *

3371   * Revision 1.150  2007/02/05 13:32:49  waltercruz

3372   * Changing double quotes to single quotes

3373   *

3374   * Revision 1.149  2007/01/26 04:52:53  fplanque

3375   * clean comment popups (skins 2.0)

3376   *

3377   * Revision 1.148  2007/01/26 02:12:06  fplanque

3378   * cleaner popup windows

3379   *

3380   * Revision 1.147  2007/01/23 03:46:24  fplanque

3381   * cleaned up presentation

3382   *

3383   * Revision 1.146  2007/01/19 10:45:42  fplanque

3384   * images everywhere :D

3385   * At this point the photoblogging code can be considered operational.

3386   *

3387   * Revision 1.145  2007/01/11 19:29:50  blueyed

3388   * Fixed E_NOTICE when using the "excerpt" feature

3389   *

3390   * Revision 1.144  2006/12/26 00:08:29  fplanque

3391   * wording

3392   *

3393   * Revision 1.143  2006/12/21 22:35:28  fplanque

3394   * No regression. But a change in usage. The more link must be configured in the skin.

3395   * Renderers cannot side-effect on the more tag any more and that actually makes the whole thing safer.

3396   *

3397   * Revision 1.142  2006/12/20 13:57:34  blueyed

3398   * TODO about regression because of pre-rendering and the <!--more--> tag

3399   *

3400   * Revision 1.141  2006/12/18 13:31:12  fplanque

3401   * fixed broken more tag

3402   *

3403   * Revision 1.140  2006/12/16 01:30:46  fplanque

3404   * Setting to allow/disable email subscriptions on a per blog basis

3405   *

3406   * Revision 1.139  2006/12/15 22:59:05  fplanque

3407   * doc

3408   *

3409   * Revision 1.138  2006/12/14 22:26:31  blueyed

3410   * Fixed E_NOTICE and displaying of pings into $Messages (though "hackish")

3411   *

3412   * Revision 1.137  2006/12/12 02:53:56  fplanque

3413   * Activated new item/comments controllers + new editing navigation

3414   * Some things are unfinished yet. Other things may need more testing.

3415   *

3416   * Revision 1.136  2006/12/07 23:13:11  fplanque

3417   * @var needs to have only one argument: the variable type

3418   * Otherwise, I can't code!

3419   *

3420   * Revision 1.135  2006/12/06 23:55:53  fplanque

3421   * hidden the dead body of the sidebar plugin + doc

3422   *

3423   * Revision 1.134  2006/12/05 14:28:29  blueyed

3424   * Fixed wordcount==0 handling; has been saved as NULL

3425   *

3426   * Revision 1.133  2006/12/05 06:38:40  blueyed

3427   * doc

3428   *

3429   * Revision 1.132  2006/12/05 00:39:56  fplanque

3430   * fixed some more permalinks/archive links

3431   *

3432   * Revision 1.131  2006/12/05 00:34:39  blueyed

3433   * Implemented custom "None" option text in DataObjectCache; Added for $ItemStatusCache, $GroupCache, UserCache and BlogCache; Added custom text for Item::priority_options()

3434   *

3435   * Revision 1.130  2006/12/04 20:52:40  blueyed

3436   * typo

3437   *

3438   * Revision 1.129  2006/12/04 19:57:58  fplanque

3439   * How often must I fix the weekly archives until they stop bugging me?

3440   *

3441   * Revision 1.128  2006/12/04 19:41:11  fplanque

3442   * Each blog can now have its own "archive mode" settings

3443   *

3444   * Revision 1.127  2006/12/03 18:15:32  fplanque

3445   * doc

3446   *

3447   * Revision 1.126  2006/12/01 20:04:31  blueyed

3448   * Renamed Plugins_admin::validate_list() to validate_renderer_list()

3449   *

3450   * Revision 1.125  2006/12/01 19:46:42  blueyed

3451   * Moved Plugins::validate_list() to Plugins_admin class; added stub in Plugins, because at least the starrating_plugin uses it

3452   *

3453   * Revision 1.124  2006/11/28 20:04:11  blueyed

3454   * No edit link, if ID==0 to avoid confusion in preview, see http://forums.b2evolution.net/viewtopic.php?p=47422#47422

3455   *

3456   * Revision 1.123  2006/11/24 18:27:24  blueyed

3457   * Fixed link to b2evo CVS browsing interface in file docblocks

3458   *

3459   * Revision 1.122  2006/11/22 20:48:58  blueyed

3460   * Added Item::get_Chapters() and Item::get_main_Chapter(); refactorized

3461   *

3462   * Revision 1.121  2006/11/22 20:12:18  blueyed

3463   * Use $format param in Item::categories()

3464   *

3465   * Revision 1.120  2006/11/19 22:17:42  fplanque

3466   * minor / doc

3467   *

3468   * Revision 1.119  2006/11/19 16:07:31  blueyed

3469   * Fixed saving empty renderers list. This should also fix the saving of "default" instead of the explicit renderer list

3470   *

3471   * Revision 1.118  2006/11/17 18:36:23  blueyed

3472   * dbchanges param for AfterItemUpdate, AfterItemInsert, AfterCommentUpdate and AfterCommentInsert

3473   *

3474   * Revision 1.117  2006/11/13 20:49:52  fplanque

3475   * doc/cleanup :/

3476   *

3477   * Revision 1.116  2006/11/10 20:14:11  blueyed

3478   * doc, fix

3479   *

3480   * Revision 1.115  2006/11/02 16:12:49  blueyed

3481   * MFB

3482   *

3483   * Revision 1.114  2006/11/02 16:01:00  blueyed

3484   * doc

3485   *

3486   * Revision 1.113  2006/10/29 18:33:23  blueyed

3487   * doc fix

3488   *

3489   * Revision 1.112  2006/10/23 22:19:02  blueyed

3490   * Fixed/unified encoding of redirect_to param. Use just rawurlencode() and no funky &amp; replacements

3491   *

3492   * Revision 1.111  2006/10/18 00:03:51  blueyed

3493   * Some forgotten url_rel_to_same_host() additions

3494   */
3495  ?>


Généré le : Thu Nov 29 23:58:50 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics