Show
Ignore:
Timestamp:
09/06/06 09:01:54 (6 years ago)
Author:
benoitg
Message:

filter content type according to various criteria. Will be used more
extensively in the profile manager.

  • Content manager: Use content type filter to only allow Simple

content types (Content without metadata) to be used for metadata.

banner adds, or any other image rotation. Size constraints not yet
implemented

  • Move externally maintained class.phpmailer.php, class.smtp.php

into lib where they belong

  • DateTime?.php: Make class handle an empty date sensibly.
  • Network.php: Show the network again when there is only one.

It was confusing in some screens.

  • page.php: Clarify error message, and set a more reasonnable

paging cascade:

5 min, 30 min, 2 hours, 1 day, 1 week, 1 month

  • Finally fix #127
  • At last, working content scheduled display and expiration for

ContentGroups?. Archiving does not yet have a UI. Content that expires
will simply seem to disapear.

  • Fix #247 (somebody filed a bug before I commited, conveniently

saving me the need to describe it).

  • The Fix for #106 in [1089] returned non-objects, causing error

messages and not displaying what it was meant to display.

Used Guest instead of Annonymous, which will probably be

used for different purpose in the future.

This re-fix does not include duplicate counting yet.

Splash users are not the only users that could log-in multiple times.

I don't have a staging server here, a fix will be

commited in a few minutes if something goes wrong.

  • Cleanup coments.
  • Sync schemas
Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/wifidog-auth/wifidog/classes/Content/ContentGroup/ContentGroup.php

    r1088 r1090  
    4848 */ 
    4949require_once ('classes/Content/ContentGroup/ContentGroupElement.php'); 
     50require_once ('classes/ContentTypeFilter.php'); 
    5051 
    5152/** 
     
    5960class ContentGroup extends Content { 
    6061 
    61     private $CONTENT_ORDERING_MODES = array ( 
    62         'RANDOM' => "Pick content elements randomly", 
    63         'PSEUDO_RANDOM' => "Pick content elements randomly, but do not display the same content twice untill all content has been seen", 
    64         'SEQUENTIAL' => "Pick content elements in sequential order" 
    65     ); 
    66     private $CONTENT_CHANGES_ON_MODES = array ( 
    67         'ALWAYS' => "Content always rotates", 
    68         'NEXT_DAY' => "Content rotates once per day", 
    69         'NEXT_LOGIN' => "Content rotates once per session", 
    70         'NEXT_NODE' => "Content rotates each time you change node" 
    71     ); 
    72     private $ALLOW_REPEAT_MODES = array ( 
    73         'YES' => "Content can be shown more than once", 
    74         'NO' => "Content can only be shown once", 
    75         'ONCE_PER_NODE' => "Content can be shown more than once, but not at the same node" 
    76     ); 
    77  
    78     protected $is_artistic_content; 
    79     protected $is_locative_content; 
    80  
    81     // is_expandable is ONLY for internal use, it use normally only set by the constructor 
    82     private $is_expandable = true; 
    83     // this is the actual publicly available status ( so if is_expandable == true it CANNOT be true ) 
    84     private $expand_status = false; 
    85     private $temporary_display_num_elements; 
    86  
    87     private $content_selection_mode; 
    88     private $content_group_row; 
    89  
    90     protected function __construct($content_id) { 
    91         // Define globals 
    92         global $db; 
    93  
    94         // Init values 
    95         $row = null; 
    96  
    97         parent :: __construct($content_id); 
    98  
    99         $content_id = $db->escapeString($content_id); 
    100  
    101         $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'"; 
    102         $db->execSqlUniqueRes($sql, $row, false); 
    103         if ($row == null) { 
    104             /*Since the parent Content exists, the necessary data in content_group had not yet been created */ 
    105             $sql = "INSERT INTO content_group (content_group_id) VALUES ('$content_id')"; 
    106             $db->execSqlUpdate($sql, false); 
    107             $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'"; 
    108             $db->execSqlUniqueRes($sql, $row, false); 
    109             if ($row == null) { 
    110                 throw new Exception(_("The content with the following id could not be found in the database: ") . $content_id); 
    111             } 
    112  
    113         } 
    114  
    115         $this->content_group_row = $row; 
    116  
    117         // These are for internal use only ( private and protected methods ) for dealing with expanding content 
    118         $this->setTemporaryDisplayNumElements(null); 
    119     } 
    120  
    121     /** Is the content group artistic in nature? 
    122      * @return true or false */ 
    123     public function isArtisticContent() { 
    124         if ($this->content_group_row['is_artistic_content'] == 't') { 
    125             $retval = true; 
    126         } else { 
    127             $retval = false; 
    128         } 
    129         return $retval; 
    130     } 
    131  
    132     /** Set if the content group is artistic in nature, 
    133      * @param $is_artistic_content true or false*/ 
    134     public function setIsArtisticContent($is_artistic_content) { 
    135         if ($is_artistic_content != $this->isArtisticContent()) /* Only update database if there is an actual change */ { 
    136             $is_artistic_content ? $is_artistic_content_sql = 'TRUE' : $is_artistic_content_sql = 'FALSE'; 
    137  
    138             global $db; 
    139             $db->execSqlUpdate("UPDATE content_group SET is_artistic_content = $is_artistic_content_sql WHERE content_group_id = '$this->id'", false); 
    140             $this->refresh(); 
    141         } 
    142  
    143     } 
    144  
    145     /** Does the content shown or generated by the content group directly related to where it is viewed from? 
    146      * @return true or false */ 
    147     public function isLocativeContent() { 
    148         if ($this->content_group_row['is_locative_content'] == 't') { 
    149             $retval = true; 
    150         } else { 
    151             $retval = false; 
    152         } 
    153         return $retval; 
    154     } 
    155  
    156     /** Set if the content group is locative 
    157      * @param $is_locative_content true or false 
    158      * */ 
    159     public function setIsLocativeContent($is_locative_content) { 
    160         if ($is_locative_content != $this->isLocativeContent()) /* Only update database if there is an actual change */ { 
    161             $is_locative_content ? $is_locative_content_sql = 'TRUE' : $is_locative_content_sql = 'FALSE'; 
    162  
    163             global $db; 
    164             $db->execSqlUpdate("UPDATE content_group SET is_locative_content = $is_locative_content_sql WHERE content_group_id = '$this->id'", false); 
    165             $this->refresh(); 
    166         } 
    167  
    168     } 
    169  
    170     /** In what order is the content displayed to the user 
    171     * @return string, a key of CONTENT_SELECTION_MODES */ 
    172     public function getContentOrderingMode() { 
    173         return $this->content_group_row['content_ordering_mode']; 
    174     } 
    175  
    176     /** In what order is the content displayed to the user 
    177      * @param $content_ordering_mode One of the CONTENT_ORDERING_MODES constants defined in the class 
    178      * @return true if successfull 
    179      * */ 
    180     protected function setContentOrderingMode($content_ordering_mode, & $errormsg = null) { 
    181         $retval = false; 
    182         if (isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode]) && $content_ordering_mode != $this->getContentOrderingMode()) /* Only update database if the mode is valid and there is an actual change */ { 
    183             global $db; 
    184             $content_ordering_mode = $db->escapeString($content_ordering_mode); 
    185             $db->execSqlUpdate("UPDATE content_group SET content_ordering_mode = '$content_ordering_mode' WHERE content_group_id = '$this->id'", false); 
    186             $this->refresh(); 
    187             $retval = true; 
    188         } 
    189         elseif (!isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode])) { 
    190             $errormsg = _("Invalid content selection mode (must be part of CONTENT_ORDERING_MODES)"); 
    191             $retval = false; 
    192         } else { 
    193             /* Successfull, but nothing modified */ 
    194             $retval = true; 
    195         } 
    196         return $retval; 
    197     } 
    198  
    199     /** When does the content rotate? 
    200     * @return string, a key of CONTENT_SELECTION_MODES */ 
    201     public function getContentChangesOnMode() { 
    202         return $this->content_group_row['content_changes_on_mode']; 
    203     } 
    204  
    205     /** When does the content rotate? 
    206      * @param $content_changes_on_mode One of the content_changes_on_modeS constants defined in the class 
    207      * @return true if successfull 
    208      * */ 
    209     protected function setContentChangesOnMode($content_changes_on_mode, & $errormsg = null) { 
    210         $retval = false; 
    211         if (isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode]) && $content_changes_on_mode != $this->getContentChangesOnMode()) /* Only update database if the mode is valid and there is an actual change */ { 
    212             global $db; 
    213             $content_changes_on_mode = $db->escapeString($content_changes_on_mode); 
    214             $db->execSqlUpdate("UPDATE content_group SET content_changes_on_mode = '$content_changes_on_mode' WHERE content_group_id = '$this->id'", false); 
    215             $this->refresh(); 
    216             $retval = true; 
    217         } 
    218         elseif (!isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode])) { 
    219             $errormsg = _("Invalid content selection mode (must be part of CONTENT_CHANGES_ON_MODES)"); 
    220             $retval = false; 
    221         } else { 
    222             /* Successfull, but nothing modified */ 
    223             $retval = true; 
    224         } 
    225         return $retval; 
    226     } 
    227  
    228     /** Can the same content be shown twice 
    229      * @return 'YES', 'NO', 'ONCE_PER_NODE' */ 
    230     public function getAllowRepeat() { 
    231         return $this->content_group_row['allow_repeat']; 
    232     } 
    233  
    234     /** When does the content rotate? 
    235      * @param $allow_repeat One of the allow_repeatS constants defined in the class 
    236      * @return true if successfull 
    237      * */ 
    238     protected function setAllowRepeat($allow_repeat, & $errormsg = null) { 
    239         $retval = false; 
    240         if (isset ($this->ALLOW_REPEAT_MODES[$allow_repeat]) && $allow_repeat != $this->getAllowRepeat()) /* Only update database if the mode is valid and there is an actual change */ { 
    241             global $db; 
    242             $allow_repeat = $db->escapeString($allow_repeat); 
    243             $db->execSqlUpdate("UPDATE content_group SET allow_repeat = '$allow_repeat' WHERE content_group_id = '$this->id'", false); 
    244             $this->refresh(); 
    245             $retval = true; 
    246         } 
    247         elseif (!isset ($this->ALLOW_REPEAT_MODES[$allow_repeat])) { 
    248             $errormsg = _("Invalid content selection mode (must be part of ALLOW_REPEAT_MODES)"); 
    249             $retval = false; 
    250         } else { 
    251             /* Successfull, but nothing modified */ 
    252             $retval = true; 
    253         } 
    254         return $retval; 
    255     } 
    256  
    257     /** How many element should be picked for display at once? 
    258     * @return integer */ 
    259     public function getDisplayNumElements() { 
    260         if ($this->temporary_display_num_elements == null) 
    261             return $this->content_group_row['display_num_elements']; 
    262         else 
    263             return $this->temporary_display_num_elements; 
    264     } 
    265  
    266     /** How many element should be picked for display at once? 
    267     * @param $display_num_elements integer, must be greater than zero. 
    268     * @return true if successfull 
    269     * */ 
    270     protected function setDisplayNumElements($display_num_elements, & $errormsg = null) { 
    271         $retval = false; 
    272         if (($display_num_elements > 0) && $display_num_elements != $this->getDisplayNumElements()) /* Only update database if the mode is valid and there is an actual change */ { 
    273             global $db; 
    274             $display_num_elements = $db->escapeString($display_num_elements); 
    275             $db->execSqlUpdate("UPDATE content_group SET display_num_elements = '$display_num_elements' WHERE content_group_id = '$this->id'", false); 
    276             $this->refresh(); 
    277             $retval = true; 
    278         } 
    279         elseif ($display_num_elements <= 0) { 
    280             $errormsg = _("You must display at least one element"); 
    281             $retval = false; 
    282         } else { 
    283             /* Successfull, but nothing modified */ 
    284             $retval = true; 
    285         } 
    286         return $retval; 
    287     } 
    288  
    289     /** 
    290      * This will a temporary limit ( NOT ACTUALLY STORED IN DATABASE ) 
    291      * Use getDisplayNumElements to get the number of elements that can be shown 
    292      * at once 
    293      */ 
    294     private function setTemporaryDisplayNumElements($temporary_num_elements) { 
    295         $this->temporary_display_num_elements = $temporary_num_elements; 
    296     } 
    297  
    298     public function getAdminUI($subclass_admin_interface = null, $title = null) { 
    299         $html = ''; 
    300         $html .= "<fieldset class='admin_element_group'>\n"; 
    301         $html .= "<legend>" . sprintf(_("%s configuration"), get_class($this)) . "</legend>\n"; 
    302  
    303         /* is_artistic_content */ 
    304         $html .= "<li class='admin_element_item_container'>\n"; 
    305         $html .= "<div class='admin_element_label'>Is artistic content?: </div>\n"; 
    306         $html .= "<div class='admin_element_data'>\n"; 
    307         $name = "content_group_" . $this->id . "_is_artistic_content"; 
    308         $this->isArtisticContent() ? $checked = 'CHECKED' : $checked = ''; 
    309         $html .= "<input type='checkbox' name='$name' $checked>\n"; 
    310         $html .= "</div>\n"; 
    311         $html .= "</li>\n"; 
    312  
    313         /* is_locative_content */ 
    314         $html .= "<li class='admin_element_item_container'>\n"; 
    315         $html .= "<div class='admin_element_label'>" . ("Is locative content?") . ": </div>\n"; 
    316         $html .= "<div class='admin_element_data'>\n"; 
    317         $name = "content_group_" . $this->id . "_is_locative_content"; 
    318         $this->isLocativeContent() ? $checked = 'CHECKED' : $checked = ''; 
    319         $html .= "<input type='checkbox' name='$name' $checked>\n"; 
    320         $html .= "</div>\n"; 
    321         $html .= "</li>\n"; 
    322  
    323         /* content_ordering_mode */ 
    324         $html .= "<li class='admin_element_item_container'>\n"; 
    325         $html .= "<div class='admin_element_label'>" . _("In what order should the content displayed?") . ": </div>\n"; 
    326         $html .= "<div class='admin_element_data'>\n"; 
    327         $name = "content_group_" . $this->id . "_content_ordering_mode"; 
    328  
    329         $i = 0; 
    330         $tab = null; 
    331         foreach ($this->CONTENT_ORDERING_MODES as $select_mode_id => $select_mode_descr) { 
    332             $tab[$i][0] = $select_mode_id; 
    333             $tab[$i][1] = $select_mode_descr; 
    334             $i++; 
    335         } 
    336         $html .= FormSelectGenerator :: generateFromArray($tab, $this->getContentOrderingMode(), $name, null, false); 
    337         $html .= "</div>\n"; 
    338         $html .= "</li>\n"; 
    339  
    340         /*content_changes_on_mode */ 
    341         $html .= "<li class='admin_element_item_container'>\n"; 
    342         $html .= "<div class='admin_element_label'>" . _("When does the content rotate?") . ": </div>\n"; 
    343         $html .= "<div class='admin_element_data'>\n"; 
    344         $name = "content_group_" . $this->id . "_content_changes_on_mode"; 
    345         $i = 0; 
    346         $tab = null; 
    347         foreach ($this->CONTENT_CHANGES_ON_MODES as $select_mode_id => $select_mode_descr) { 
    348             $tab[$i][0] = $select_mode_id; 
    349             $tab[$i][1] = $select_mode_descr; 
    350             $i++; 
    351         } 
    352         $html .= FormSelectGenerator :: generateFromArray($tab, $this->getContentChangesOnMode(), $name, null, false); 
    353         $html .= "</div>\n"; 
    354         $html .= "</li>\n"; 
    355  
    356         /* allow_repeat*/ 
    357         $html .= "<li class='admin_element_item_container'>\n"; 
    358         $html .= "<div class='admin_element_label'>" . _("Can content be shown more than once to the same user?") . ": </div>\n"; 
    359         $html .= "<div class='admin_element_data'>\n"; 
    360         $name = "content_group_" . $this->id . "_allow_repeat"; 
    361         $i = 0; 
    362         $tab = null; 
    363         foreach ($this->ALLOW_REPEAT_MODES as $select_mode_id => $select_mode_descr) { 
    364             $tab[$i][0] = $select_mode_id; 
    365             $tab[$i][1] = $select_mode_descr; 
    366             $i++; 
    367         } 
    368         $html .= FormSelectGenerator :: generateFromArray($tab, $this->getAllowRepeat(), $name, null, false); 
    369         $html .= "</div>\n"; 
    370         $html .= "</li>\n"; 
    371  
    372         /*display_num_elements*/ 
    373         $html .= "<li class='admin_element_item_container'>\n"; 
    374         $html .= "<div class='admin_element_label'>" . ("Pick how many elements for each display?") . ": </div>\n"; 
    375         $html .= "<div class='admin_element_data'>\n"; 
    376         $name = "content_group_" . $this->id . "_display_num_elements"; 
    377         $value = $this->getDisplayNumElements(); 
    378         $html .= "<input type='text' size='2' value='$value' name='$name'>\n"; 
    379         $html .= "</div>\n"; 
    380         $html .= "</li>\n"; 
    381         $html .= "</fieldset>\n"; 
    382  
    383         $html .= "<li class='admin_element_item_container'>\n"; 
    384         $html .= "<fieldset class='admin_element_group'>\n"; 
    385         $html .= "<legend>" . sprintf(_("%s display element list"), get_class($this)) . "</legend>\n"; 
    386  
    387         /* content_group_element*/ 
    388  
    389         $html .= "<ul class='admin_element_list'>\n"; 
    390         foreach ($this->getElements() as $element) { 
    391             $html .= "<li class='admin_element_item_container'>\n"; 
    392             $html .= $element->getAdminUI(null, sprintf(_("%s %d"), get_class($element), $element->getDisplayOrder())); 
    393             $html .= "<div class='admin_element_tools'>\n"; 
    394             $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase"; 
    395             $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s %d"), get_class($element), $element->getDisplayOrder()) . "'>"; 
    396             $html .= "</div>\n"; 
    397             $html .= "</li>\n"; 
    398         } 
    399         $html .= "<li class='admin_element_item_container'>\n"; 
    400         $html .= self :: getNewContentUI("content_group_{$this->id}_new_element") . "<br>"; 
    401         $html .= "</li>\n"; 
    402         $html .= "<li class='admin_element_item_container'>\n"; 
    403         $html .= self :: getSelectExistingContentUI("content_group_{$this->id}_existing_element", "AND content_id != '$this->id'"); 
    404         $html .= "</li>\n"; 
    405         $html .= "</ul>\n"; 
    406         $html .= "</fieldset>\n"; 
    407         $html .= "</li>\n"; 
    408         $html .= $subclass_admin_interface; 
    409         return parent :: getAdminUI($html, $title); 
    410     } 
    411  
    412     function processAdminUI() { 
    413         // Init values 
    414         $errmsg = null; 
    415  
    416         if ($this->isOwner(User :: getCurrentUser()) || User :: getCurrentUser()->isSuperAdmin()) { 
    417             parent :: processAdminUI(); 
    418  
    419             /* is_artistic_content */ 
    420             $name = "content_group_" . $this->id . "_is_artistic_content"; 
    421             !empty ($_REQUEST[$name]) ? $this->setIsArtisticContent(true) : $this->setIsArtisticContent(false); 
    422  
    423             /* is_locative_content */ 
    424             $name = "content_group_" . $this->id . "_is_locative_content"; 
    425             !empty ($_REQUEST[$name]) ? $this->setIsLocativeContent(true) : $this->setIsLocativeContent(false); 
    426  
    427             /* content_ordering_mode */ 
    428             $name = "content_group_" . $this->id . "_content_ordering_mode"; 
    429             $this->setContentOrderingMode(FormSelectGenerator :: getResult($name, null)); 
    430  
    431             /*content_changes_on_mode */ 
    432             $name = "content_group_" . $this->id . "_content_changes_on_mode"; 
    433             $this->setContentChangesOnMode(FormSelectGenerator :: getResult($name, null)); 
    434  
    435             /* allow_repeat*/ 
    436             $name = "content_group_" . $this->id . "_allow_repeat"; 
    437             $this->setAllowRepeat(FormSelectGenerator :: getResult($name, null)); 
    438  
    439             /*display_num_elements*/ 
    440             $name = "content_group_" . $this->id . "_display_num_elements"; 
    441             $this->setDisplayNumElements($_REQUEST[$name]); 
    442  
    443             /* content_group_element */ 
    444             foreach ($this->getElements() as $element) { 
    445                 $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase"; 
    446                 if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) { 
    447                     $element->delete($errmsg); 
    448                 } else { 
    449                     $element->processAdminUI(); 
    450                 } 
    451             } 
    452  
    453             // The two following calls will either add a new element or add an existing one ( depending on what button the user clicked 
    454             /* We explicitely call the ContentGroupElement version of processNewContentUI */ 
    455             ContentGroupElement :: processNewContentUI("content_group_{$this->id}_new_element", $this); 
    456             // Last parameters allows for existing content ( if any was selected ) 
    457             ContentGroupElement :: processNewContentUI("content_group_{$this->id}_existing_element", $this, true); 
    458         } 
    459     } 
    460  
    461     /** Is this Content element displayable at this hotspot 
    462      * @param $node Node, optionnal 
    463      * @return true or false */ 
    464     public function isDisplayableAt($node) { 
    465         $old_curent_node = Node :: getCurrentNode(); 
    466         Node :: setCurrentNode($node); 
    467  
    468         if (count($this->getDisplayElements()) > 0) { 
    469             $retval = true; 
    470         } else { 
    471             $retval = false; 
    472         } 
    473  
    474         if ($old_curent_node != null) { 
    475             Node :: setCurrentNode($old_curent_node); 
    476         } 
    477  
    478         return $retval; 
    479     } 
    480  
    481     /**Get the next element or elements to be displayed, depending on the display mode 
    482     * @return an array of ContentGroupElement or an empty arrray */ 
    483     function getDisplayElements() { 
    484         // Define globals 
    485         global $db; 
    486  
    487         // Init values 
    488         $retval = array (); 
    489         $user = User :: getCurrentUser(); 
    490         $redisplay_rows = null; 
    491         $last_order_row = null; 
    492         $element_rows = null; 
    493  
    494         if ($user) { 
    495             $user_id = $user->getId(); 
    496         } else { 
    497             $user_id = ''; 
    498         } 
    499         $node = Node :: getCurrentNode(); 
    500         if ($node) { 
    501             $node_id = $node->getId(); 
    502         } else { 
    503             $node_id = ''; 
    504         } 
    505         $display_num_elements = $this->getDisplayNumElements(); 
    506  
    507         /** First, find if we have content to display again because we haven't passed the rotation period */ 
    508         /*  'ALWAYS' => "Content always rotates" 
    509          *  'NEXT_DAY' => "Content rotates once per day" 
    510          *  'NEXT_LOGIN' => "Content rotates once per session" 
    511          *  'NEXT_NODE' => "Content rotates each time you change node"*/ 
    512         $content_changes_on_mode = $this->getContentChangesOnMode(); 
    513  
    514         $redisplay_objects = array (); 
    515         if ($content_changes_on_mode != 'ALWAYS') { 
    516             $sql = "SELECT content_group_element_id FROM content_group_element \n"; 
    517             $sql .= "JOIN content_display_log ON (content_group_element_id=content_id) \n"; 
    518             $sql .= " WHERE content_group_id='$this->id' \n"; 
    519  
    520             if ($content_changes_on_mode == 'NEXT_DAY') { 
    521                 $sql .= "AND date_trunc('day', last_display_timestamp) = date_trunc('day', CURRENT_DATE) \n"; 
    522             } 
    523             if ($content_changes_on_mode == 'NEXT_LOGIN') { 
    524                 /**@todo Must fix, this will fail if the user never really connected from a hotspot... */ 
    525                 $sql .= "AND last_display_timestamp > (SELECT timestamp_in FROM connections WHERE user_id='$user_id' ORDER BY timestamp_in DESC LIMIT 1) \n"; 
    526             } 
    527             if ($content_changes_on_mode == 'NEXT_NODE') { 
    528                 /** We find the close time of the last connection from another node */ 
    529                 $sql .= "AND last_display_timestamp > (SELECT timestamp_out FROM connections WHERE user_id='$user_id' AND node_id != '$node_id' ORDER BY timestamp_in DESC LIMIT 1) \n"; 
    530             } 
    531             /* There usually won't be more than one, but if there is, we want the most recents */ 
    532             $sql .= " ORDER BY last_display_timestamp DESC "; 
    533             $db->execSql($sql, $redisplay_rows, false); 
    534             $redisplay_objects = array (); 
    535             if ($redisplay_rows != null) { 
    536                 foreach ($redisplay_rows as $redisplay_row) { 
    537                     $object = self :: getObject($redisplay_row['content_group_element_id']); 
    538                     if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */ 
    539                         { 
    540                         $redisplay_objects[] = $object; 
    541                     } 
    542                 } 
    543             } 
    544             /* Pick the proper number of elements to be re-displayed */ 
    545             $redisplay_objects = array_slice($redisplay_objects, 0, $display_num_elements); 
    546  
    547         } 
    548  
    549         $new_objects = array (); 
    550         if (count($redisplay_objects) < $display_num_elements) { 
    551             /* There aren't enough elements to redisplay, We need new content */ 
    552  
    553             $sql_base = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' \n"; 
     62        private $CONTENT_ORDERING_MODES = array ( 
     63                'RANDOM' => "Pick content elements randomly", 
     64                'PSEUDO_RANDOM' => "Pick content elements randomly, but do not display the same content twice untill all content has been seen", 
     65                'SEQUENTIAL' => "Pick content elements in sequential order" 
     66        ); 
     67        private $CONTENT_CHANGES_ON_MODES = array ( 
     68                'ALWAYS' => "Content always rotates", 
     69                'NEXT_DAY' => "Content rotates once per day", 
     70                'NEXT_LOGIN' => "Content rotates once per session", 
     71                'NEXT_NODE' => "Content rotates each time you change node" 
     72        ); 
     73        private $ALLOW_REPEAT_MODES = array ( 
     74                'YES' => "Content can be shown more than once", 
     75                'NO' => "Content can only be shown once", 
     76                'ONCE_PER_NODE' => "Content can be shown more than once, but not at the same node" 
     77        ); 
     78 
     79        protected $is_artistic_content; 
     80        protected $is_locative_content; 
     81 
     82        // is_expandable is ONLY for internal use, it use normally only set by the constructor 
     83        private $is_expandable = true; 
     84        // this is the actual publicly available status ( so if is_expandable == true it CANNOT be true ) 
     85        private $expand_status = false; 
     86        private $temporary_display_num_elements; 
     87 
     88        private $content_selection_mode; 
     89        private $content_group_row; 
     90        /** ContentTypeFilter object */ 
     91        protected $allowed_content_types; 
     92 
     93        protected function __construct($content_id) { 
     94                // Define globals 
     95                global $db; 
     96 
     97                // Init values 
     98                $row = null; 
     99 
     100                parent :: __construct($content_id); 
     101 
     102                $content_id = $db->escapeString($content_id); 
     103 
     104                $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'"; 
     105                $db->execSqlUniqueRes($sql, $row, false); 
     106                if ($row == null) { 
     107                        /*Since the parent Content exists, the necessary data in content_group had not yet been created */ 
     108                        $sql = "INSERT INTO content_group (content_group_id) VALUES ('$content_id')"; 
     109                        $db->execSqlUpdate($sql, false); 
     110                        $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'"; 
     111                        $db->execSqlUniqueRes($sql, $row, false); 
     112                        if ($row == null) { 
     113                                throw new Exception(_("The content with the following id could not be found in the database: ") . $content_id); 
     114                        } 
     115 
     116                } 
     117 
     118                $this->content_group_row = $row; 
     119 
     120                // These are for internal use only ( private and protected methods ) for dealing with expanding content 
     121                $this->setTemporaryDisplayNumElements(null); 
     122        } 
     123 
     124        /** Set if the content group is artistic in nature, 
     125        * @param $is_artistic_content true or false*/ 
     126        public function setAllowedContentTypes(ContentTypeFilter $allowed_content_types) { 
     127                $this->allowed_content_types = $allowed_content_types; 
     128 
     129        } 
     130        /** Is the content group artistic in nature? 
     131         * @return true or false */ 
     132        public function isArtisticContent() { 
     133                if ($this->content_group_row['is_artistic_content'] == 't') { 
     134                        $retval = true; 
     135                } else { 
     136                        $retval = false; 
     137                } 
     138                return $retval; 
     139        } 
     140 
     141        /** Set if the content group is artistic in nature, 
     142         * @param $is_artistic_content true or false*/ 
     143        public function setIsArtisticContent($is_artistic_content) { 
     144                if ($is_artistic_content != $this->isArtisticContent()) /* Only update database if there is an actual change */ { 
     145                        $is_artistic_content ? $is_artistic_content_sql = 'TRUE' : $is_artistic_content_sql = 'FALSE'; 
     146 
     147                        global $db; 
     148                        $db->execSqlUpdate("UPDATE content_group SET is_artistic_content = $is_artistic_content_sql WHERE content_group_id = '$this->id'", false); 
     149                        $this->refresh(); 
     150                } 
     151 
     152        } 
     153 
     154        /** Does the content shown or generated by the content group directly related to where it is viewed from? 
     155         * @return true or false */ 
     156        public function isLocativeContent() { 
     157                if ($this->content_group_row['is_locative_content'] == 't') { 
     158                        $retval = true; 
     159                } else { 
     160                        $retval = false; 
     161                } 
     162                return $retval; 
     163        } 
     164 
     165        /** Set if the content group is locative 
     166         * @param $is_locative_content true or false 
     167         * */ 
     168        public function setIsLocativeContent($is_locative_content) { 
     169                if ($is_locative_content != $this->isLocativeContent()) /* Only update database if there is an actual change */ { 
     170                        $is_locative_content ? $is_locative_content_sql = 'TRUE' : $is_locative_content_sql = 'FALSE'; 
     171 
     172                        global $db; 
     173                        $db->execSqlUpdate("UPDATE content_group SET is_locative_content = $is_locative_content_sql WHERE content_group_id = '$this->id'", false); 
     174                        $this->refresh(); 
     175                } 
     176 
     177        } 
     178 
     179        /** In what order is the content displayed to the user 
     180        * @return string, a key of CONTENT_SELECTION_MODES */ 
     181        public function getContentOrderingMode() { 
     182                return $this->content_group_row['content_ordering_mode']; 
     183        } 
     184 
     185        /** In what order is the content displayed to the user 
     186         * @param $content_ordering_mode One of the CONTENT_ORDERING_MODES constants defined in the class 
     187         * @return true if successfull 
     188         * */ 
     189        protected function setContentOrderingMode($content_ordering_mode, & $errormsg = null) { 
     190                $retval = false; 
     191                if (isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode]) && $content_ordering_mode != $this->getContentOrderingMode()) /* Only update database if the mode is valid and there is an actual change */ { 
     192                        global $db; 
     193                        $content_ordering_mode = $db->escapeString($content_ordering_mode); 
     194                        $db->execSqlUpdate("UPDATE content_group SET content_ordering_mode = '$content_ordering_mode' WHERE content_group_id = '$this->id'", false); 
     195                        $this->refresh(); 
     196                        $retval = true; 
     197                } 
     198                elseif (!isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode])) { 
     199                        $errormsg = _("Invalid content selection mode (must be part of CONTENT_ORDERING_MODES)"); 
     200                        $retval = false; 
     201                } else { 
     202                        /* Successfull, but nothing modified */ 
     203                        $retval = true; 
     204                } 
     205                return $retval; 
     206        } 
     207 
     208        /** When does the content rotate? 
     209        * @return string, a key of CONTENT_SELECTION_MODES */ 
     210        public function getContentChangesOnMode() { 
     211                return $this->content_group_row['content_changes_on_mode']; 
     212        } 
     213 
     214        /** When does the content rotate? 
     215         * @param $content_changes_on_mode One of the content_changes_on_modeS constants defined in the class 
     216         * @return true if successfull 
     217         * */ 
     218        protected function setContentChangesOnMode($content_changes_on_mode, & $errormsg = null) { 
     219                $retval = false; 
     220                if (isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode]) && $content_changes_on_mode != $this->getContentChangesOnMode()) /* Only update database if the mode is valid and there is an actual change */ { 
     221                        global $db; 
     222                        $content_changes_on_mode = $db->escapeString($content_changes_on_mode); 
     223                        $db->execSqlUpdate("UPDATE content_group SET content_changes_on_mode = '$content_changes_on_mode' WHERE content_group_id = '$this->id'", false); 
     224                        $this->refresh(); 
     225                        $retval = true; 
     226                } 
     227                elseif (!isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode])) { 
     228                        $errormsg = _("Invalid content selection mode (must be part of CONTENT_CHANGES_ON_MODES)"); 
     229                        $retval = false; 
     230                } else { 
     231                        /* Successfull, but nothing modified */ 
     232                        $retval = true; 
     233                } 
     234                return $retval; 
     235        } 
     236 
     237        /** Can the same content be shown twice 
     238         * @return 'YES', 'NO', 'ONCE_PER_NODE' */ 
     239        public function getAllowRepeat() { 
     240                return $this->content_group_row['allow_repeat']; 
     241        } 
     242 
     243        /** When does the content rotate? 
     244         * @param $allow_repeat One of the allow_repeatS constants defined in the class 
     245         * @return true if successfull 
     246         * */ 
     247        protected function setAllowRepeat($allow_repeat, & $errormsg = null) { 
     248                $retval = false; 
     249                if (isset ($this->ALLOW_REPEAT_MODES[$allow_repeat]) && $allow_repeat != $this->getAllowRepeat()) /* Only update database if the mode is valid and there is an actual change */ { 
     250                        global $db; 
     251                        $allow_repeat = $db->escapeString($allow_repeat); 
     252                        $db->execSqlUpdate("UPDATE content_group SET allow_repeat = '$allow_repeat' WHERE content_group_id = '$this->id'", false); 
     253                        $this->refresh(); 
     254                        $retval = true; 
     255                } 
     256                elseif (!isset ($this->ALLOW_REPEAT_MODES[$allow_repeat])) { 
     257                        $errormsg = _("Invalid content selection mode (must be part of ALLOW_REPEAT_MODES)"); 
     258                        $retval = false; 
     259                } else { 
     260                        /* Successfull, but nothing modified */ 
     261                        $retval = true; 
     262                } 
     263                return $retval; 
     264        } 
     265 
     266        /** How many element should be picked for display at once? 
     267        * @return integer */ 
     268        public function getDisplayNumElements() { 
     269                if ($this->temporary_display_num_elements == null) 
     270                        return $this->content_group_row['display_num_elements']; 
     271                else 
     272                        return $this->temporary_display_num_elements; 
     273        } 
     274 
     275        /** How many element should be picked for display at once? 
     276        * @param $display_num_elements integer, must be greater than zero. 
     277        * @return true if successfull 
     278        * */ 
     279        protected function setDisplayNumElements($display_num_elements, & $errormsg = null) { 
     280                $retval = false; 
     281                if (($display_num_elements > 0) && $display_num_elements != $this->getDisplayNumElements()) /* Only update database if the mode is valid and there is an actual change */ { 
     282                        global $db; 
     283                        $display_num_elements = $db->escapeString($display_num_elements); 
     284                        $db->execSqlUpdate("UPDATE content_group SET display_num_elements = '$display_num_elements' WHERE content_group_id = '$this->id'", false); 
     285                        $this->refresh(); 
     286                        $retval = true; 
     287                } 
     288                elseif ($display_num_elements <= 0) { 
     289                        $errormsg = _("You must display at least one element"); 
     290                        $retval = false; 
     291                } else { 
     292                        /* Successfull, but nothing modified */ 
     293                        $retval = true; 
     294                } 
     295                return $retval; 
     296        } 
     297 
     298        /** 
     299         * This will a temporary limit ( NOT ACTUALLY STORED IN DATABASE ) 
     300         * Use getDisplayNumElements to get the number of elements that can be shown 
     301         * at once 
     302         */ 
     303        private function setTemporaryDisplayNumElements($temporary_num_elements) { 
     304                $this->temporary_display_num_elements = $temporary_num_elements; 
     305        } 
     306 
     307        public function getAdminUI($subclass_admin_interface = null, $title = null) { 
     308                $html = ''; 
     309                $html .= "<fieldset class='admin_element_group'>\n"; 
     310                $html .= "<legend>" . sprintf(_("%s configuration"), get_class($this)) . "</legend>\n"; 
     311 
     312                /* is_artistic_content */ 
     313                $html .= "<li class='admin_element_item_container'>\n"; 
     314                $html .= "<div class='admin_element_label'>Is artistic content?: </div>\n"; 
     315                $html .= "<div class='admin_element_data'>\n"; 
     316                $name = "content_group_" . $this->id . "_is_artistic_content"; 
     317                $this->isArtisticContent() ? $checked = 'CHECKED' : $checked = ''; 
     318                $html .= "<input type='checkbox' name='$name' $checked>\n"; 
     319                $html .= "</div>\n"; 
     320                $html .= "</li>\n"; 
     321 
     322                /* is_locative_content */ 
     323                $html .= "<li class='admin_element_item_container'>\n"; 
     324                $html .= "<div class='admin_element_label'>" . ("Is locative content?") . ": </div>\n"; 
     325                $html .= "<div class='admin_element_data'>\n"; 
     326                $name = "content_group_" . $this->id . "_is_locative_content"; 
     327                $this->isLocativeContent() ? $checked = 'CHECKED' : $checked = ''; 
     328                $html .= "<input type='checkbox' name='$name' $checked>\n"; 
     329                $html .= "</div>\n"; 
     330                $html .= "</li>\n"; 
     331 
     332                /* content_ordering_mode */ 
     333                $html .= "<li class='admin_element_item_container'>\n"; 
     334                $html .= "<div class='admin_element_label'>" . _("In what order should the content displayed?") . ": </div>\n"; 
     335                $html .= "<div class='admin_element_data'>\n"; 
     336                $name = "content_group_" . $this->id . "_content_ordering_mode"; 
     337 
     338                $i = 0; 
     339                $tab = null; 
     340                foreach ($this->CONTENT_ORDERING_MODES as $select_mode_id => $select_mode_descr) { 
     341                        $tab[$i][0] = $select_mode_id; 
     342                        $tab[$i][1] = $select_mode_descr; 
     343                        $i++; 
     344                } 
     345                $html .= FormSelectGenerator :: generateFromArray($tab, $this->getContentOrderingMode(), $name, null, false); 
     346                $html .= "</div>\n"; 
     347                $html .= "</li>\n"; 
     348 
     349                /*content_changes_on_mode */ 
     350                $html .= "<li class='admin_element_item_container'>\n"; 
     351                $html .= "<div class='admin_element_label'>" . _("When does the content rotate?") . ": </div>\n"; 
     352                $html .= "<div class='admin_element_data'>\n"; 
     353                $name = "content_group_" . $this->id . "_content_changes_on_mode"; 
     354                $i = 0; 
     355                $tab = null; 
     356                foreach ($this->CONTENT_CHANGES_ON_MODES as $select_mode_id => $select_mode_descr) { 
     357                        $tab[$i][0] = $select_mode_id; 
     358                        $tab[$i][1] = $select_mode_descr; 
     359                        $i++; 
     360                } 
     361                $html .= FormSelectGenerator :: generateFromArray($tab, $this->getContentChangesOnMode(), $name, null, false); 
     362                $html .= "</div>\n"; 
     363                $html .= "</li>\n"; 
     364 
     365                /* allow_repeat*/ 
     366                $html .= "<li class='admin_element_item_container'>\n"; 
     367                $html .= "<div class='admin_element_label'>" . _("Can content be shown more than once to the same user?") . ": </div>\n"; 
     368                $html .= "<div class='admin_element_data'>\n"; 
     369                $name = "content_group_" . $this->id . "_allow_repeat"; 
     370                $i = 0; 
     371                $tab = null; 
     372                foreach ($this->ALLOW_REPEAT_MODES as $select_mode_id => $select_mode_descr) { 
     373                        $tab[$i][0] = $select_mode_id; 
     374                        $tab[$i][1] = $select_mode_descr; 
     375                        $i++; 
     376                } 
     377                $html .= FormSelectGenerator :: generateFromArray($tab, $this->getAllowRepeat(), $name, null, false); 
     378                $html .= "</div>\n"; 
     379                $html .= "</li>\n"; 
     380 
     381                /*display_num_elements*/ 
     382                $html .= "<li class='admin_element_item_container'>\n"; 
     383                $html .= "<div class='admin_element_label'>" . ("Pick how many elements for each display?") . ": </div>\n"; 
     384                $html .= "<div class='admin_element_data'>\n"; 
     385                $name = "content_group_" . $this->id . "_display_num_elements"; 
     386                $value = $this->getDisplayNumElements(); 
     387                $html .= "<input type='text' size='2' value='$value' name='$name'>\n"; 
     388                $html .= "</div>\n"; 
     389                $html .= "</li>\n"; 
     390                $html .= "</fieldset>\n"; 
     391 
     392                $html .= "<li class='admin_element_item_container'>\n"; 
     393                $html .= "<fieldset class='admin_element_group'>\n"; 
     394                $html .= "<legend>" . sprintf(_("%s display element list"), get_class($this)) . "</legend>\n"; 
     395 
     396                /* content_group_element*/ 
     397 
     398                $html .= "<ul class='admin_element_list'>\n"; 
     399                foreach ($this->getElements("AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n") as $element) { 
     400                        $html .= "<li class='admin_element_item_container'>\n"; 
     401                        $html .= $element->getAdminUI(null, sprintf(_("%s %d"), get_class($element), $element->getDisplayOrder())); 
     402                        $html .= "<div class='admin_element_tools'>\n"; 
     403                        $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase"; 
     404                        $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s %d"), get_class($element), $element->getDisplayOrder()) . "'>"; 
     405                        $html .= "</div>\n"; 
     406                        $html .= "</li>\n"; 
     407                } 
     408                $html .= "<li class='admin_element_item_container'>\n"; 
     409                $html .= self :: getNewContentUI("content_group_{$this->id}_new_element", $this->allowed_content_types); 
     410                $html .= "</li>\n"; 
     411                $html .= "<li class='admin_element_item_container'>\n"; 
     412                $html .= self :: getSelectExistingContentUI("content_group_{$this->id}_existing_element", "AND content_id != '$this->id'", $this->allowed_content_types); 
     413                $html .= "</li>\n"; 
     414                $html .= "</ul>\n"; 
     415                $html .= "</fieldset>\n"; 
     416                $html .= "</li>\n"; 
     417                $html .= $subclass_admin_interface; 
     418                return parent :: getAdminUI($html, $title); 
     419        } 
     420 
     421        function processAdminUI() { 
     422                // Init values 
     423                $errmsg = null; 
     424 
     425                if ($this->isOwner(User :: getCurrentUser()) || User :: getCurrentUser()->isSuperAdmin()) { 
     426                        parent :: processAdminUI(); 
     427 
     428                        /* is_artistic_content */ 
     429                        $name = "content_group_" . $this->id . "_is_artistic_content"; 
     430                        !empty ($_REQUEST[$name]) ? $this->setIsArtisticContent(true) : $this->setIsArtisticContent(false); 
     431 
     432                        /* is_locative_content */ 
     433                        $name = "content_group_" . $this->id . "_is_locative_content"; 
     434                        !empty ($_REQUEST[$name]) ? $this->setIsLocativeContent(true) : $this->setIsLocativeContent(false); 
     435 
     436                        /* content_ordering_mode */ 
     437                        $name = "content_group_" . $this->id . "_content_ordering_mode"; 
     438                        $this->setContentOrderingMode(FormSelectGenerator :: getResult($name, null)); 
     439 
     440                        /*content_changes_on_mode */ 
     441                        $name = "content_group_" . $this->id . "_content_changes_on_mode"; 
     442                        $this->setContentChangesOnMode(FormSelectGenerator :: getResult($name, null)); 
     443 
     444                        /* allow_repeat*/ 
     445                        $name = "content_group_" . $this->id . "_allow_repeat"; 
     446                        $this->setAllowRepeat(FormSelectGenerator :: getResult($name, null)); 
     447 
     448                        /*display_num_elements*/ 
     449                        $name = "content_group_" . $this->id . "_display_num_elements"; 
     450                        $this->setDisplayNumElements($_REQUEST[$name]); 
     451 
     452                        /* content_group_element */ 
     453                        foreach ($this->getElements("AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n") as $element) { 
     454                                $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase"; 
     455                                if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) { 
     456                                        $element->delete($errmsg); 
     457                                } else { 
     458                                        $element->processAdminUI(); 
     459                                } 
     460                        } 
     461 
     462                        // The two following calls will either add a new element or add an existing one ( depending on what button the user clicked 
     463                        /* We explicitely call the ContentGroupElement version of processNewContentUI */ 
     464                        ContentGroupElement :: processNewContentUI("content_group_{$this->id}_new_element", $this); 
     465                        // Last parameters allows for existing content ( if any was selected ) 
     466                        ContentGroupElement :: processNewContentUI("content_group_{$this->id}_existing_element", $this, true); 
     467                } 
     468        } 
     469 
     470        /** Is this Content element displayable at this hotspot 
     471         * @param $node Node, optionnal 
     472         * @return true or false */ 
     473        public function isDisplayableAt($node) { 
     474                $old_curent_node = Node :: getCurrentNode(); 
     475                Node :: setCurrentNode($node); 
     476 
     477                if (count($this->getDisplayElements()) > 0) { 
     478                        $retval = true; 
     479                } else { 
     480                        $retval = false; 
     481                } 
     482 
     483                if ($old_curent_node != null) { 
     484                        Node :: setCurrentNode($old_curent_node); 
     485                } 
     486 
     487                return $retval; 
     488        } 
     489 
     490        /**Get the next element or elements to be displayed, depending on the display mode 
     491        * @return an array of ContentGroupElement or an empty arrray */ 
     492        function getDisplayElements() { 
     493                // Define globals 
     494                global $db; 
     495 
     496                // Init values 
     497                $retval = array (); 
     498                $user = User :: getCurrentUser(); 
     499                $redisplay_rows = null; 
     500                $last_order_row = null; 
     501                $element_rows = null; 
     502 
     503                if ($user) { 
     504                        $user_id = $user->getId(); 
     505                } else { 
     506                        $user_id = ''; 
     507                } 
     508                $node = Node :: getCurrentNode(); 
     509                if ($node) { 
     510                        $node_id = $node->getId(); 
     511                } else { 
     512                        $node_id = ''; 
     513                } 
     514                $display_num_elements = $this->getDisplayNumElements(); 
     515                /*  'ALWAYS' => "Content always rotates" 
     516                 *  'NEXT_DAY' => "Content rotates once per day" 
     517                 *  'NEXT_LOGIN' => "Content rotates once per session" 
     518                 *  'NEXT_NODE' => "Content rotates each time you change node"*/ 
     519                $content_changes_on_mode = $this->getContentChangesOnMode(); 
     520 
     521                $sql_time_restrictions = " AND (valid_from_timestamp IS NULL OR valid_from_timestamp <= CURRENT_TIMESTAMP) AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n"; 
     522                /** First, find if we have content to display again because we haven't passed the rotation period */ 
     523                $redisplay_objects = array (); 
     524                if ($content_changes_on_mode != 'ALWAYS') { 
     525                        $sql = "SELECT content_group_element_id FROM content_group_element \n"; 
     526                        $sql .= "JOIN content_display_log ON (content_group_element_id=content_id) \n"; 
     527                        $sql .= " WHERE content_group_id='$this->id' \n"; 
     528                        $sql .= $sql_time_restrictions; 
     529 
     530                        if ($content_changes_on_mode == 'NEXT_DAY') { 
     531                                $sql .= "AND date_trunc('day', last_display_timestamp) = date_trunc('day', CURRENT_DATE) \n"; 
     532                        } 
     533                        if ($content_changes_on_mode == 'NEXT_LOGIN') { 
     534                                /**@todo Must fix, this will fail if the user never really connected from a hotspot... */ 
     535                                $sql .= "AND last_display_timestamp > (SELECT timestamp_in FROM connections WHERE user_id='$user_id' ORDER BY timestamp_in DESC LIMIT 1) \n"; 
     536                        } 
     537                        if ($content_changes_on_mode == 'NEXT_NODE') { 
     538                                /** We find the close time of the last connection from another node */ 
     539                                $sql .= "AND last_display_timestamp > (SELECT timestamp_out FROM connections WHERE user_id='$user_id' AND node_id != '$node_id' ORDER BY timestamp_in DESC LIMIT 1) \n"; 
     540                        } 
     541                        /* There usually won't be more than one, but if there is, we want the most recents */ 
     542                        $sql .= " ORDER BY last_display_timestamp DESC "; 
     543                        $db->execSql($sql, $redisplay_rows, false); 
     544                        $redisplay_objects = array (); 
     545                        if ($redisplay_rows != null) { 
     546                                foreach ($redisplay_rows as $redisplay_row) { 
     547                                        $object = self :: getObject($redisplay_row['content_group_element_id']); 
     548                                        if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */ 
     549                                                { 
     550                                                $redisplay_objects[] = $object; 
     551                                        } 
     552                                } 
     553                        } 
     554                        /* Pick the proper number of elements to be re-displayed */ 
     555                        $redisplay_objects = array_slice($redisplay_objects, 0, $display_num_elements); 
     556 
     557                } 
     558 
     559                $new_objects = array (); 
     560                if (count($redisplay_objects) < $display_num_elements) { 
     561                        /* There aren't enough elements to redisplay, We need new content */ 
     562 
     563                        $sql_base = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' \n"; 
     564                        $sql_base .= $sql_time_restrictions; 
    554565                        $sql = $sql_base; 
    555566 
    556             /*'YES' => "Content can be shown more than once", 'NO' => "Content can only be shown once", 'ONCE_PER_NODE' => "Content can be shown more than once, but not at the same node"*/ 
    557             $allow_repeat = $this->getAllowRepeat(); 
    558             if ($allow_repeat == 'NO') { 
    559                 $sql_repeat .= "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n"; 
    560             } 
    561             elseif ($allow_repeat == 'ONCE_PER_NODE') { 
    562                 $sql_repeat .= "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id' AND  node_id = '$node_id') \n"; 
    563             } 
    564             else 
    565             { 
    566             $sql_repeat=null; 
    567             } 
    568             $sql .= $sql_repeat; 
    569  
    570             $content_ordering_mode = $this->getContentOrderingMode(); 
    571             if ($content_ordering_mode == 'SEQUENTIAL') { 
    572                 $order_by = ' ORDER BY display_order '; 
    573                 //Find the last content displayed 
    574                 $sql_last_order = "SELECT display_order FROM content_group_element \n"; 
    575                 $sql_last_order .= "JOIN content_display_log ON (content_group_element_id=content_id) \n"; 
    576                 $sql_last_order .= " WHERE content_group_id='$this->id' \n"; 
    577                 $sql_last_order .= " ORDER BY last_display_timestamp DESC LIMIT 1"; 
    578                 $db->execSqlUniqueRes($sql_last_order, $last_order_row, false); 
    579                 if ($last_order_row['display_order'] != null) { 
    580                     $last_order = $last_order_row['display_order']; 
    581                 } else { 
    582                     $last_order = 0; 
    583                 } 
    584             } else { 
    585                 $order_by = ' '; 
    586             } 
    587             $sql .= $order_by; 
    588  
    589 $element_rows = null; 
    590 if ($content_ordering_mode == 'PSEUDO_RANDOM') { 
    591     //Special case, first get only the rows that haven't been displayed before' 
    592                     $sql_no_repeat = " AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n"; 
    593                     $db->execSql($sql_base.$sql_no_repeat, $element_rows, false); 
    594 } 
    595 //Normal case, or there wasn't any undisplayed content in PSEUDO_RANDOM 
    596  if ($element_rows == null) { 
    597             $db->execSql($sql, $element_rows, false); 
    598  } 
    599             if ($element_rows == null) { 
    600                 $element_rows = array (); 
    601             } 
    602             foreach ($element_rows as $element_row) { 
    603                 $object = self :: getObject($element_row['content_group_element_id']); 
    604                 if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */ 
    605                     { 
    606                     $new_objects[] = $object; 
    607                 } 
    608             } 
    609  
    610             if ($content_ordering_mode == 'RANDOM' || $content_ordering_mode == 'PSEUDO_RANDOM') { 
    611                 shuffle($new_objects); 
    612             } 
    613             elseif ($content_ordering_mode == 'SEQUENTIAL') { 
    614                 foreach ($new_objects as $object) { 
    615                     if ($object->getDisplayOrder() <= $last_order) { 
    616                         array_push($new_objects, array_shift($new_objects)); 
    617                         //echo " Pushed ".$object->getDisplayOrder(); 
    618                     } 
    619                 } 
    620             } 
    621  
    622             /** Pick the proper number of elements */ 
    623             $num_to_pick = $display_num_elements -count($redisplay_objects); 
    624             $new_objects = array_slice($new_objects, 0, $num_to_pick); 
    625         } 
    626         /* 
    627         echo "<pre>Redisplay: "; 
    628         print_r($redisplay_objects); 
    629         echo "New objects: "; 
    630         print_r($new_objects); 
    631         echo "</pre>"; 
    632         */ 
    633         $retval = array_merge($new_objects, $redisplay_objects); 
    634         //echo count($retval).' returned <br>'; 
    635         return $retval; 
    636     } 
    637  
    638     /** 
    639      * This attribute is for internal use ( to tell if a certain class could be 
    640      * expanded ) 
    641      * @param $status boolean 
    642      */ 
    643     protected function setIsExpandable($status) { 
    644         if (is_bool($status)) 
    645             $this->is_expandable = $status; 
    646     } 
    647  
    648     /** 
    649      * Tells if this object could be expanded 
    650      */ 
    651     protected function isExpandable() { 
    652         return $this->is_expandable; 
    653     } 
    654  
    655     /** 
    656      * Will expand content ONLY if allowed by isExpandable (which is protected) 
    657      * @param $status boolean 
    658      */ 
    659     public function setExpandStatus($status) { 
    660         if ($this->isExpandable() && is_bool($status)) { 
    661             //TODO: Try to find a better solution to this problem... 
    662             if ($status == true) 
    663                 $this->setTemporaryDisplayNumElements(3000); 
    664             else 
    665                 $this->setTemporaryDisplayNumElements(null); 
    666             $this->expand_status = $status; 
    667         } 
    668     } 
    669  
    670     /** 
    671      * Get the expand status 
    672      * 
    673      * WARNING 
    674      * NON expandable contents ie PatternLanguage will NEVER return true 
    675      */ 
    676     public function getExpandStatus() { 
    677         if ($this->expand_status == null) 
    678             return false; 
    679         return $this->expand_status; 
    680     } 
    681  
    682     /** Retreives the user interface of this object.  Anything that overrides this method should call the parent method with it's output at the END of processing. 
    683      * @param $subclass_admin_interface Html content of the interface element of a children 
    684      * @param boolean $hide_elements allows the child class ( for example 
    685      * Pattern Language) to tell the content group not to display elements ) for 
    686      * elements that need to be hidden before subscription 
    687      * @return The HTML fragment for this interface */ 
    688     public function getUserUI($subclass_user_interface = null, $hide_elements = false) { 
    689         $html = ''; 
    690         $html .= "<div class='user_ui_container  " . get_class($this) . "'>\n"; 
    691  
    692         if ($hide_elements == false) { 
    693             $display_elements = $this->getDisplayElements(); 
    694             if (count($display_elements) > 0) { 
    695                 foreach ($display_elements as $display_element) { 
    696                     // If the content group logging is disabled, all the children will inherit this property temporarly 
    697                     if ($this->getLoggingStatus() == false) 
    698                         $display_element->setLoggingStatus(false); 
    699                     $html .= $display_element->getUserUI(); 
    700                 } 
    701             } else { 
    702                 $html .= '<p class="warningmsg">' . _("Sorry, no elements available at this hotspot or all elements of the content group have already been shown") . "</p>\n"; 
    703             } 
    704         } 
    705  
    706         $html .= $subclass_user_interface; 
    707         $html .= "</div>\n"; 
    708  
    709         return parent :: getUserUI($html); 
    710     } 
    711  
    712     /**Get all elements 
    713      * @return an array of ContentGroupElement or an empty arrray */ 
    714     function getElements() { 
    715         // Define globals 
    716         global $db; 
    717  
    718         // Init values 
    719         $retval = array (); 
    720         $element_rows = null; 
    721  
    722         $sql = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' ORDER BY display_order"; 
    723         $db->execSql($sql, $element_rows, false); 
    724         if ($element_rows != null) { 
    725             foreach ($element_rows as $element_row) { 
    726                 $retval[] = self :: getObject($element_row['content_group_element_id']); 
    727             } 
    728         } 
    729         return $retval; 
    730     } 
    731  
    732     /** 
    733      * Delete this Content from the database 
    734      */ 
    735     public function delete(& $errmsg) { 
    736         if ($this->isPersistent() == false) { 
    737             foreach ($this->getElements() as $element) { 
    738                 $element->delete($errmsg); 
    739             } 
    740         } 
    741         return parent::delete($errmsg); 
    742     } 
    743  
    744     /** Reloads the object from the database.  Should normally be called after a set operation. 
    745     * This function is private because calling it from a subclass will call the 
    746     * constructor from the wrong scope */ 
    747     private function refresh() { 
    748         $this->__construct($this->id); 
    749     } 
     567                        /*'YES' => "Content can be shown more than once", 'NO' => "Content can only be shown once", 'ONCE_PER_NODE' => "Content can be shown more than once, but not at the same node"*/ 
     568                        $allow_repeat = $this->getAllowRepeat(); 
     569                        if ($allow_repeat == 'NO') { 
     570                                $sql_repeat .= "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n"; 
     571                        } 
     572                        elseif ($allow_repeat == 'ONCE_PER_NODE') { 
     573                                $sql_repeat .= "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id' AND  node_id = '$node_id') \n"; 
     574                        } else { 
     575                                $sql_repeat = null; 
     576                        } 
     577                        $sql .= $sql_repeat; 
     578 
     579                        $content_ordering_mode = $this->getContentOrderingMode(); 
     580                        if ($content_ordering_mode == 'SEQUENTIAL') { 
     581                                $order_by = ' ORDER BY display_order '; 
     582                                //Find the last content displayed 
     583                                $sql_last_order = "SELECT display_order FROM content_group_element \n"; 
     584                                $sql_last_order .= "JOIN content_display_log ON (content_group_element_id=content_id) \n"; 
     585                                $sql_last_order .= " WHERE content_group_id='$this->id' \n"; 
     586                                $sql_last_order .= " ORDER BY last_display_timestamp DESC LIMIT 1"; 
     587                                $db->execSqlUniqueRes($sql_last_order, $last_order_row, false); 
     588                                if ($last_order_row['display_order'] != null) { 
     589                                        $last_order = $last_order_row['display_order']; 
     590                                } else { 
     591                                        $last_order = 0; 
     592                                } 
     593                        } else { 
     594                                $order_by = ' '; 
     595                        } 
     596                        $sql .= $order_by; 
     597 
     598                        $element_rows = null; 
     599                        if ($content_ordering_mode == 'PSEUDO_RANDOM') { 
     600                                //Special case, first get only the rows that haven't been displayed before' 
     601                                $sql_no_repeat = " AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n"; 
     602                                $db->execSql($sql_base . $sql_no_repeat, $element_rows, false); 
     603                        } 
     604                        //Normal case, or there wasn't any undisplayed content in PSEUDO_RANDOM 
     605                        if ($element_rows == null) { 
     606                                $db->execSql($sql, $element_rows, false); 
     607                        } 
     608                        if ($element_rows == null) { 
     609                                $element_rows = array (); 
     610                        } 
     611                        foreach ($element_rows as $element_row) { 
     612                                $object = self :: getObject($element_row['content_group_element_id']); 
     613                                if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */ 
     614                                        { 
     615                                        $new_objects[] = $object; 
     616                                } 
     617                        } 
     618 
     619                        if ($content_ordering_mode == 'RANDOM' || $content_ordering_mode == 'PSEUDO_RANDOM') { 
     620                                shuffle($new_objects); 
     621                        } 
     622                        elseif ($content_ordering_mode == 'SEQUENTIAL') { 
     623                                foreach ($new_objects as $object) { 
     624                                        if ($object->getDisplayOrder() <= $last_order) { 
     625                                                array_push($new_objects, array_shift($new_objects)); 
     626                                                //echo " Pushed ".$object->getDisplayOrder(); 
     627                                        } 
     628                                } 
     629                        } 
     630 
     631                        /** Pick the proper number of elements */ 
     632                        $num_to_pick = $display_num_elements -count($redisplay_objects); 
     633                        $new_objects = array_slice($new_objects, 0, $num_to_pick); 
     634                } 
     635                /* 
     636                echo "<pre>Redisplay: "; 
     637                print_r($redisplay_objects); 
     638                echo "New objects: "; 
     639                print_r($new_objects); 
     640                echo "</pre>"; 
     641                */ 
     642                $retval = array_merge($new_objects, $redisplay_objects); 
     643                //echo count($retval).' returned <br>'; 
     644                return $retval; 
     645        } 
     646 
     647        /** 
     648         * This attribute is for internal use ( to tell if a certain class could be 
     649         * expanded ) 
     650         * @param $status boolean 
     651         */ 
     652        protected function setIsExpandable($status) { 
     653                if (is_bool($status)) 
     654                        $this->is_expandable = $status; 
     655        } 
     656 
     657        /** 
     658         * Tells if this object could be expanded 
     659         */ 
     660        protected function isExpandable() { 
     661                return $this->is_expandable; 
     662        } 
     663 
     664        /** 
     665         * Will expand content ONLY if allowed by isExpandable (which is protected) 
     666         * @param $status boolean 
     667         */ 
     668        public function setExpandStatus($status) { 
     669                if ($this->isExpandable() && is_bool($status)) { 
     670                        //TODO: Try to find a better solution to this problem... 
     671                        if ($status == true) 
     672                                $this->setTemporaryDisplayNumElements(3000); 
     673                        else 
     674                                $this->setTemporaryDisplayNumElements(null); 
     675                        $this->expand_status = $status; 
     676                } 
     677        } 
     678 
     679        /** 
     680         * Get the expand status 
     681         * 
     682         * WARNING 
     683         * NON expandable contents ie PatternLanguage will NEVER return true 
     684         */ 
     685        public function getExpandStatus() { 
     686                if ($this->expand_status == null) 
     687                        return false; 
     688                return $this->expand_status; 
     689        } 
     690 
     691        /** Retreives the user interface of this object.  Anything that overrides this method should call the parent method with it's output at the END of processing. 
     692         * @param $subclass_admin_interface Html content of the interface element of a children 
     693         * @param boolean $hide_elements allows the child class ( for example 
     694         * Pattern Language) to tell the content group not to display elements ) for 
     695         * elements that need to be hidden before subscription 
     696         * @return The HTML fragment for this interface */ 
     697        public function getUserUI($subclass_user_interface = null, $hide_elements = false) { 
     698                $html = ''; 
     699                $html .= "<div class='user_ui_container  " . get_class($this) . "'>\n"; 
     700 
     701                if ($hide_elements == false) { 
     702                        $display_elements = $this->getDisplayElements(); 
     703                        if (count($display_elements) > 0) { 
     704                                foreach ($display_elements as $display_element) { 
     705                                        // If the content group logging is disabled, all the children will inherit this property temporarly 
     706                                        if ($this->getLoggingStatus() == false) 
     707                                                $display_element->setLoggingStatus(false); 
     708                                        $html .= $display_element->getUserUI(); 
     709                                } 
     710                        } else { 
     711                                $html .= '<p class="warningmsg">' . _("Sorry, no elements available at this hotspot or all elements of the content group have already been shown") . "</p>\n"; 
     712                        } 
     713                } 
     714 
     715                $html .= $subclass_user_interface; 
     716                $html .= "</div>\n"; 
     717 
     718                return parent :: getUserUI($html); 
     719        } 
     720 
     721        /**Get all elements 
     722         * @return an array of ContentGroupElement or an empty arrray */ 
     723        function getElements($additional_where=null) { 
     724                // Define globals 
     725                global $db; 
     726 
     727                // Init values 
     728                $retval = array (); 
     729                $element_rows = null; 
     730 
     731                $sql = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' $additional_where ORDER BY display_order"; 
     732                $db->execSql($sql, $element_rows, false); 
     733                if ($element_rows != null) { 
     734                        foreach ($element_rows as $element_row) { 
     735                                $element = self :: getObject($element_row['content_group_element_id']); 
     736                                $retval[] = self :: getObject($element_row['content_group_element_id']); 
     737                        } 
     738                } 
     739                return $retval; 
     740        } 
     741 
     742        /** 
     743         * Delete this Content from the database 
     744         */ 
     745        public function delete(& $errmsg) { 
     746                if ($this->isPersistent() == false) { 
     747                        foreach ($this->getElements() as $element) { 
     748                                $element->delete($errmsg); 
     749                        } 
     750                } 
     751                return parent :: delete($errmsg); 
     752        } 
     753 
     754        /** Reloads the object from the database.  Should normally be called after a set operation. 
     755        * This function is private because calling it from a subclass will call the 
     756        * constructor from the wrong scope */ 
     757        private function refresh() { 
     758                $this->__construct($this->id); 
     759        } 
    750760 
    751761}