root/trunk/wifidog-auth/wifidog/classes/Content/ContentGroup/ContentGroup.php @ 1147

Revision 1147, 32.6 KB (checked in by benoitg, 7 years ago)

Sync with my main developpement environement:

  • Performance optimization pass: Collect a few low hagning

fruits

by implementing basic instance caching for classes Server,

Network, Nodes, Content and User.

It's no replacement for a real ORM, but is cuts way down on

duplicate SQL queries and

object instanciation for a single request.

  • Langstring.php: Slight performance optimization for admin

interface, support for proper modification date.

  • File.php: Update to use unified content modification date.
  • get_stylesheet.php: Implement browser caching
  • ContentGroup?.php: Get rid of is_locative and is_artistic

metadata. Will eventually be replaced by something better.

  • Node.php, Utils.php: Sort by case insensitive name, and show

gateway id in node selectors

all images in the group.

  • ContentReport?.php: Finish basic content reporting. Note that for content that displayed before the logging fixes

commited 2006-11-14, the display count will be underestimated:

Display before then was only counted once per user per node.

Also File clickthrough wasn't logged (includes hyperlinked pictures).

  • Content.php: Implement a Key-Value Pair infrastructure. In

the future, this will often allow adding new content types without

having to modify the schema. See the Content class

documentation.

  • Fix various small UI issues.
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2
3
4/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5
6// +-------------------------------------------------------------------+
7// | WiFiDog Authentication Server                                     |
8// | =============================                                     |
9// |                                                                   |
10// | The WiFiDog Authentication Server is part of the WiFiDog captive  |
11// | portal suite.                                                     |
12// +-------------------------------------------------------------------+
13// | PHP version 5 required.                                           |
14// +-------------------------------------------------------------------+
15// | Homepage:     http://www.wifidog.org/                             |
16// | Source Forge: http://sourceforge.net/projects/wifidog/            |
17// +-------------------------------------------------------------------+
18// | This program is free software; you can redistribute it and/or     |
19// | modify it under the terms of the GNU General Public License as    |
20// | published by the Free Software Foundation; either version 2 of    |
21// | the License, or (at your option) any later version.               |
22// |                                                                   |
23// | This program is distributed in the hope that it will be useful,   |
24// | but WITHOUT ANY WARRANTY; without even the implied warranty of    |
25// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     |
26// | GNU General Public License for more details.                      |
27// |                                                                   |
28// | You should have received a copy of the GNU General Public License |
29// | along with this program; if not, contact:                         |
30// |                                                                   |
31// | Free Software Foundation           Voice:  +1-617-542-5942        |
32// | 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        |
33// | Boston, MA  02111-1307,  USA       gnu@gnu.org                    |
34// |                                                                   |
35// +-------------------------------------------------------------------+
36
37/**
38 * @package    WiFiDogAuthServer
39 * @subpackage ContentClasses
40 * @author     Benoit Grégoire <bock@step.polymtl.ca>
41 * @copyright  2005-2006 Benoit Grégoire, Technologies Coeus inc.
42 * @version    Subversion $Id$
43 * @link       http://www.wifidog.org/
44 */
45
46/**
47 * Load ContentGroupElement class
48 */
49require_once ('classes/Content/ContentGroup/ContentGroupElement.php');
50require_once ('classes/ContentTypeFilter.php');
51
52/**
53 * A generic content group
54 *
55 * @package    WiFiDogAuthServer
56 * @subpackage ContentClasses
57 * @author     Benoit Grégoire <bock@step.polymtl.ca>
58 * @copyright  2005-2006 Benoit Grégoire, Technologies Coeus inc.
59 */
60class ContentGroup extends Content {
61
62    private $CONTENT_ORDERING_MODES = array (
63        'RANDOM' => "Pick content elements randomly",
64        'PSEUDO_RANDOM' => "Pick content elements randomly, but not twice until all elements have 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    // is_expandable is ONLY for internal use, it use normally only set by the constructor
80    private $is_expandable = true;
81    // this is the actual publicly available status ( so if is_expandable == true it CANNOT be true )
82    private $expand_status = false;
83    private $temporary_display_num_elements;
84    private $display_elements;
85    private $content_selection_mode;
86    private $content_group_row;
87    /** ContentTypeFilter object */
88    protected $allowed_content_types;
89
90    protected function __construct($content_id) {
91
92        $db = AbstractDb :: getObject();
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    /** Set the allowed content types for the group,
122    * @param $allowed_content_types ContentTypeFilter*/
123    public function setAllowedContentTypes(ContentTypeFilter $allowed_content_types) {
124        $this->allowed_content_types = $allowed_content_types;
125
126    }
127
128    /** In what order is the content displayed to the user
129    * @return string, a key of CONTENT_SELECTION_MODES */
130    public function getContentOrderingMode() {
131        return $this->content_group_row['content_ordering_mode'];
132    }
133
134    /** In what order is the content displayed to the user
135     * @param $content_ordering_mode One of the CONTENT_ORDERING_MODES constants defined in the class
136     * @return true if successfull
137     * */
138    protected function setContentOrderingMode($content_ordering_mode, & $errormsg = null) {
139        $retval = false;
140        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 */ {
141            $db = AbstractDb :: getObject();
142            $content_ordering_mode = $db->escapeString($content_ordering_mode);
143            $db->execSqlUpdate("UPDATE content_group SET content_ordering_mode = '$content_ordering_mode' WHERE content_group_id = '$this->id'", false);
144            $this->refresh();
145            $retval = true;
146        }
147        elseif (!isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode])) {
148            $errormsg = _("Invalid content selection mode (must be part of CONTENT_ORDERING_MODES)");
149            $retval = false;
150        } else {
151            /* Successfull, but nothing modified */
152            $retval = true;
153        }
154        return $retval;
155    }
156
157    /** When does the content rotate?
158    * @return string, a key of CONTENT_SELECTION_MODES */
159    public function getContentChangesOnMode() {
160        return $this->content_group_row['content_changes_on_mode'];
161    }
162
163    /** When does the content rotate?
164     * @param $content_changes_on_mode One of the content_changes_on_modeS constants defined in the class
165     * @return true if successfull
166     * */
167    protected function setContentChangesOnMode($content_changes_on_mode, & $errormsg = null) {
168        $retval = false;
169        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 */ {
170            $db = AbstractDb :: getObject();
171            $content_changes_on_mode = $db->escapeString($content_changes_on_mode);
172            $db->execSqlUpdate("UPDATE content_group SET content_changes_on_mode = '$content_changes_on_mode' WHERE content_group_id = '$this->id'", false);
173            $this->refresh();
174            $retval = true;
175        }
176        elseif (!isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode])) {
177            $errormsg = _("Invalid content selection mode (must be part of CONTENT_CHANGES_ON_MODES)");
178            $retval = false;
179        } else {
180            /* Successfull, but nothing modified */
181            $retval = true;
182        }
183        return $retval;
184    }
185
186    /** Can the same content be shown twice
187     * @return 'YES', 'NO', 'ONCE_PER_NODE' */
188    public function getAllowRepeat() {
189        return $this->content_group_row['allow_repeat'];
190    }
191
192    /** When does the content rotate?
193     * @param $allow_repeat One of the allow_repeatS constants defined in the class
194     * @return true if successfull
195     * */
196    protected function setAllowRepeat($allow_repeat, & $errormsg = null) {
197        $retval = false;
198        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 */ {
199            $db = AbstractDb :: getObject();
200            $allow_repeat = $db->escapeString($allow_repeat);
201            $db->execSqlUpdate("UPDATE content_group SET allow_repeat = '$allow_repeat' WHERE content_group_id = '$this->id'", false);
202            $this->refresh();
203            $retval = true;
204        }
205        elseif (!isset ($this->ALLOW_REPEAT_MODES[$allow_repeat])) {
206            $errormsg = _("Invalid content selection mode (must be part of ALLOW_REPEAT_MODES)");
207            $retval = false;
208        } else {
209            /* Successfull, but nothing modified */
210            $retval = true;
211        }
212        return $retval;
213    }
214
215    /** How many element should be picked for display at once?
216    * @return integer */
217    public function getDisplayNumElements() {
218        if ($this->temporary_display_num_elements == null)
219            return $this->content_group_row['display_num_elements'];
220        else
221            return $this->temporary_display_num_elements;
222    }
223
224    /** How many element should be picked for display at once?
225    * @param $display_num_elements integer, must be greater than zero.
226    * @return true if successfull
227    * */
228    protected function setDisplayNumElements($display_num_elements, & $errormsg = null) {
229        $retval = false;
230        if (($display_num_elements > 0) && $display_num_elements != $this->getDisplayNumElements()) /* Only update database if the mode is valid and there is an actual change */ {
231            $db = AbstractDb :: getObject();
232            $display_num_elements = $db->escapeString($display_num_elements);
233            $db->execSqlUpdate("UPDATE content_group SET display_num_elements = '$display_num_elements' WHERE content_group_id = '$this->id'", false);
234            $this->refresh();
235            $retval = true;
236        }
237        elseif ($display_num_elements <= 0) {
238            $errormsg = _("You must display at least one element");
239            $retval = false;
240        } else {
241            /* Successfull, but nothing modified */
242            $retval = true;
243        }
244        return $retval;
245    }
246
247    /**
248     * This will a temporary limit ( NOT ACTUALLY STORED IN DATABASE )
249     * Use getDisplayNumElements to get the number of elements that can be shown
250     * at once
251     */
252    private function setTemporaryDisplayNumElements($temporary_num_elements) {
253        $this->temporary_display_num_elements = $temporary_num_elements;
254    }
255
256    public function getAdminUI($subclass_admin_interface = null, $title = null) {
257        $html = '';
258        $html .= "<fieldset class='admin_element_group'>\n";
259        $html .= "<legend>" . sprintf(_("%s configuration"), get_class($this)) . "</legend>\n";
260
261        /* content_ordering_mode */
262        $html .= "<li class='admin_element_item_container'>\n";
263        $html .= "<div class='admin_element_label'>" . _("In what order should the content displayed?") . ": </div>\n";
264        $html .= "<div class='admin_element_data'>\n";
265        $name = "content_group_" . $this->id . "_content_ordering_mode";
266        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->CONTENT_ORDERING_MODES, $this->getContentOrderingMode(), $name, null, false);
267        $html .= "</div>\n";
268        $html .= "</li>\n";
269
270        /*content_changes_on_mode */
271        $html .= "<li class='admin_element_item_container'>\n";
272        $html .= "<div class='admin_element_label'>" . _("When does the content rotate?") . ": </div>\n";
273        $html .= "<div class='admin_element_data'>\n";
274        $name = "content_group_" . $this->id . "_content_changes_on_mode";
275        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->CONTENT_CHANGES_ON_MODES, $this->getContentChangesOnMode(), $name, null, false);
276        $html .= "</div>\n";
277        $html .= "</li>\n";
278
279        /* allow_repeat*/
280        $html .= "<li class='admin_element_item_container'>\n";
281        $html .= "<div class='admin_element_label'>" . _("Can content be shown more than once to the same user?") . ": </div>\n";
282        $html .= "<div class='admin_element_data'>\n";
283        $name = "content_group_" . $this->id . "_allow_repeat";
284        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->ALLOW_REPEAT_MODES, $this->getAllowRepeat(), $name, null, false);
285        $html .= "</div>\n";
286        $html .= "</li>\n";
287
288        /*display_num_elements*/
289        $html .= "<li class='admin_element_item_container'>\n";
290        $html .= "<div class='admin_element_label'>" . ("Pick how many elements for each display?") . ": </div>\n";
291        $html .= "<div class='admin_element_data'>\n";
292        $name = "content_group_" . $this->id . "_display_num_elements";
293        $value = $this->getDisplayNumElements();
294        $html .= "<input type='text' size='2' value='$value' name='$name'>\n";
295        $html .= "</div>\n";
296        $html .= "</li>\n";
297
298        /* Subclass UI */
299        $html .= $subclass_admin_interface;
300
301        $html .= "</fieldset>\n";
302
303        $html .= "<li class='admin_element_item_container'>\n";
304        $html .= "<fieldset class='admin_element_group'>\n";
305        $html .= "<legend>" . sprintf(_("%s display element list"), get_class($this)) . "</legend>\n";
306
307        /* content_group_element*/
308        $name = "content_group_" . $this->id . "_show_expired_elements_request";
309        if (empty ($_REQUEST[$name])) {
310            $showExpired = false;
311            $additionalWhere = "AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n";
312            $title_str = _("Show expired group elements");
313            $html .= "<a name='group_select' onclick=\"document.getElementById('$name').value='" . !$showExpired . "';document.generic_object_form.submit();\">{$title_str}</a>\n";
314
315        } else {
316            $showExpired = true;
317            $additionalWhere = null;
318            $title_str = _("Hide expired group elements");
319            $html .= "<a name='group_select' onclick=\"document.getElementById('$name').value='" . !$showExpired . "';document.generic_object_form.submit();\">{$title_str}</a>\n";
320
321        }
322        $html .= "<input type='hidden' name='$name' id='$name' value='$showExpired'>\n";
323        $name = "content_group_" . $this->id . "_expired_elements_shown";
324        $html .= "<input type='hidden' name='$name' id='$name' value='$showExpired'>\n";
325       
326        $html .= "<ul class='admin_element_list'>\n";
327        foreach ($this->getElements($additionalWhere) as $element) {
328            $html .= "<li class='admin_element_item_container'>\n";
329            $html .= $element->getAdminUI(null, sprintf(_("%s %d"), get_class($element), $element->getDisplayOrder()));
330            $html .= "<div class='admin_element_tools'>\n";
331            $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase";
332            $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s %d"), get_class($element), $element->getDisplayOrder()) . "'>";
333            $html .= "</div>\n";
334            $html .= "</li>\n";
335        }
336        $html .= "<li class='admin_element_item_container'>\n";
337        $html .= self :: getNewContentUI("content_group_{$this->id}_new_element", $this->allowed_content_types);
338        $html .= "</li>\n";
339        $html .= "<li class='admin_element_item_container'>\n";
340        $html .= self :: getSelectExistingContentUI("content_group_{$this->id}_existing_element", "AND content_id != '$this->id' AND is_persistent=TRUE", $this->allowed_content_types);
341        $html .= "</li>\n";
342        $html .= "</ul>\n";
343        $html .= "</fieldset>\n";
344        $html .= "</li>\n";
345        return parent :: getAdminUI($html, $title);
346    }
347
348    function processAdminUI() {
349        // Init values
350        $errmsg = null;
351
352        if ($this->isOwner(User :: getCurrentUser()) || User :: getCurrentUser()->isSuperAdmin()) {
353            parent :: processAdminUI();
354
355            /* content_ordering_mode */
356            $name = "content_group_" . $this->id . "_content_ordering_mode";
357            $this->setContentOrderingMode(FormSelectGenerator :: getResult($name, null));
358
359            /*content_changes_on_mode */
360            $name = "content_group_" . $this->id . "_content_changes_on_mode";
361            $this->setContentChangesOnMode(FormSelectGenerator :: getResult($name, null));
362
363            /* allow_repeat*/
364            $name = "content_group_" . $this->id . "_allow_repeat";
365            $this->setAllowRepeat(FormSelectGenerator :: getResult($name, null));
366
367            /*display_num_elements*/
368            $name = "content_group_" . $this->id . "_display_num_elements";
369            $this->setDisplayNumElements($_REQUEST[$name]);
370
371            /* content_group_element */
372         $name = "content_group_" . $this->id . "_expired_elements_shown";
373        if (empty ($_REQUEST[$name])) {
374            $additionalWhere = "AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n";
375        } else {
376            $additionalWhere = null;
377       }
378            foreach ($this->getElements($additionalWhere) as $element) {
379                $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase";
380                if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
381                    $element->delete($errmsg);
382                } else {
383                    $element->processAdminUI();
384                }
385            }
386
387            // The two following calls will either add a new element or add an existing one ( depending on what button the user clicked
388            /* We explicitely call the ContentGroupElement version of processNewContentUI */
389            ContentGroupElement :: processNewContentUI("content_group_{$this->id}_new_element", $this);
390            // Last parameters allows for existing content ( if any was selected )
391            ContentGroupElement :: processNewContentUI("content_group_{$this->id}_existing_element", $this, true);
392        }
393    }
394
395    /** Is this Content element displayable at this hotspot
396     * @param $node Node, optionnal
397     * @return true or false */
398    public function isDisplayableAt($node) {
399        $old_curent_node = Node :: getCurrentNode();
400        Node :: setCurrentNode($node);
401
402        if (count($this->getDisplayElements()) > 0) {
403            $retval = true;
404        } else {
405            $retval = false;
406        }
407
408        if ($old_curent_node != null) {
409            Node :: setCurrentNode($old_curent_node);
410        }
411
412        return $retval;
413    }
414
415    /**Get the next element or elements to be displayed, depending on the display mode
416    * @return an array of ContentGroupElement or an empty arrray */
417    function getDisplayElements() {
418        //This function is very expensive, cache the results
419        if (!is_array($this->display_elements)) {
420
421            $db = AbstractDb :: getObject();
422
423            // Init values
424            $retval = array ();
425            $user = User :: getCurrentUser();
426            $redisplay_rows = null;
427            $last_order_row = null;
428            $element_rows = null;
429
430            if ($user) {
431                $user_id = $user->getId();
432            } else {
433                $user_id = '';
434            }
435            $node = Node :: getCurrentNode();
436            if ($node) {
437                $node_id = $node->getId();
438            } else {
439                $node_id = '';
440            }
441            $display_num_elements = $this->getDisplayNumElements();
442            /*  'ALWAYS' => "Content always rotates"
443             *  'NEXT_DAY' => "Content rotates once per day"
444             *  'NEXT_LOGIN' => "Content rotates once per session"
445             *  'NEXT_NODE' => "Content rotates each time you change node"*/
446            $content_changes_on_mode = $this->getContentChangesOnMode();
447
448            $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";
449            /** First, find if we have content to display again because we haven't passed the rotation period */
450            $redisplay_objects = array ();
451            if ($content_changes_on_mode != 'ALWAYS') {
452                $sql = "SELECT content_group_element_id FROM content_group_element \n";
453                $sql .= "JOIN content_display_log ON (content_group_element_id=content_id) \n";
454                $sql .= " WHERE content_group_id='$this->id' \n";
455                $sql .= $sql_time_restrictions;
456
457                if ($content_changes_on_mode == 'NEXT_DAY') {
458                    $sql .= "AND date_trunc('day', last_display_timestamp) = date_trunc('day', CURRENT_DATE) \n";
459                }
460                if ($content_changes_on_mode == 'NEXT_LOGIN') {
461                    /**@todo Must fix, this will fail if the user never really connected from a hotspot... */
462                    $sql .= "AND last_display_timestamp > (SELECT timestamp_in FROM connections WHERE user_id='$user_id' ORDER BY timestamp_in DESC LIMIT 1) \n";
463                }
464                if ($content_changes_on_mode == 'NEXT_NODE') {
465                    /** We find the close time of the last connection from another node */
466                    $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";
467                }
468                /* There usually won't be more than one, but if there is, we want the most recents */
469                $sql .= " ORDER BY last_display_timestamp DESC ";
470                $db->execSql($sql, $redisplay_rows, false);
471                $redisplay_objects = array ();
472                if ($redisplay_rows != null) {
473                    foreach ($redisplay_rows as $redisplay_row) {
474                        $object = self :: getObject($redisplay_row['content_group_element_id']);
475                        if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */
476                            {
477                            $redisplay_objects[] = $object;
478                        }
479                    }
480                }
481                /* Pick the proper number of elements to be re-displayed */
482                $redisplay_objects = array_slice($redisplay_objects, 0, $display_num_elements);
483
484            }
485
486            $new_objects = array ();
487            if (count($redisplay_objects) < $display_num_elements) {
488                /* There aren't enough elements to redisplay, We need new content */
489
490                $sql_base = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' \n";
491                $sql_base .= $sql_time_restrictions;
492                $sql = $sql_base;
493
494                /*'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"*/
495                $allow_repeat = $this->getAllowRepeat();
496                if ($allow_repeat == 'NO') {
497                    $sql_repeat = "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n";
498                }
499                elseif ($allow_repeat == 'ONCE_PER_NODE') {
500                    $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";
501                } else {
502                    $sql_repeat = null;
503                }
504                $sql .= $sql_repeat;
505
506                $content_ordering_mode = $this->getContentOrderingMode();
507                if ($content_ordering_mode == 'SEQUENTIAL') {
508                    $order_by = ' ORDER BY display_order ';
509                    //Find the last content displayed
510                    $sql_last_order = "SELECT display_order FROM content_group_element \n";
511                    $sql_last_order .= "JOIN content_display_log ON (content_group_element_id=content_id) \n";
512                    $sql_last_order .= " WHERE content_group_id='$this->id' \n";
513                    $sql_last_order .= " AND user_id='$user_id' \n";
514
515                    $sql_last_order .= " ORDER BY last_display_timestamp DESC LIMIT 1";
516                    $db->execSqlUniqueRes($sql_last_order, $last_order_row, false);
517                    if ($last_order_row['display_order'] != null) {
518                        $last_order = $last_order_row['display_order'];
519                    } else {
520                        $last_order = 0;
521                    }
522                } else {
523                    $order_by = ' ';
524                }
525                $sql .= $order_by;
526
527                $element_rows = null;
528                if ($content_ordering_mode == 'PSEUDO_RANDOM') {
529                    //Special case, first get only the rows that haven't been displayed before'
530                    $sql_no_repeat = " AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n";
531                    $db->execSql($sql_base . $sql_no_repeat, $element_rows, false);
532                }
533                //Normal case, or there wasn't any undisplayed content in PSEUDO_RANDOM
534                if ($element_rows == null) {
535                    $db->execSql($sql, $element_rows, false);
536                }
537                if ($element_rows == null) {
538                    $element_rows = array ();
539                }
540                foreach ($element_rows as $element_row) {
541                    $object = self :: getObject($element_row['content_group_element_id']);
542                    if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */
543                        {
544                        $new_objects[] = $object;
545                    }
546                }
547
548                if ($content_ordering_mode == 'RANDOM' || $content_ordering_mode == 'PSEUDO_RANDOM') {
549                    shuffle($new_objects);
550                }
551                elseif ($content_ordering_mode == 'SEQUENTIAL') {
552                    foreach ($new_objects as $object) {
553                        if ($object->getDisplayOrder() <= $last_order) {
554                            array_push($new_objects, array_shift($new_objects));
555                            //echo " Pushed ".$object->getDisplayOrder();
556                        }
557                    }
558                }
559
560                /** Pick the proper number of elements */
561                $num_to_pick = $display_num_elements -count($redisplay_objects);
562                $new_objects = array_slice($new_objects, 0, $num_to_pick);
563            }
564            /*
565            echo "<pre>Redisplay: ";
566            print_r($redisplay_objects);
567            echo "New objects: ";
568            print_r($new_objects);
569            echo "</pre>";
570            */
571            $retval = array_merge($new_objects, $redisplay_objects);
572            //echo count($retval).' returned <br>';
573            $this->display_elements = $retval;
574        }
575        return $this->display_elements;
576    }
577
578    /**
579     * This attribute is for internal use ( to tell if a certain class could be
580     * expanded )
581     * @param $status boolean
582     */
583    protected function setIsExpandable($status) {
584        if (is_bool($status))
585            $this->is_expandable = $status;
586    }
587
588    /**
589     * Tells if this object could be expanded
590     */
591    protected function isExpandable() {
592        return $this->is_expandable;
593    }
594
595    /**
596     * Will expand content ONLY if allowed by isExpandable (which is protected)
597     * @param $status boolean
598     */
599    public function setExpandStatus($status) {
600        if ($this->isExpandable() && is_bool($status)) {
601            //TODO: Try to find a better solution to this problem...
602            if ($status == true)
603                $this->setTemporaryDisplayNumElements(3000);
604            else
605                $this->setTemporaryDisplayNumElements(null);
606            $this->expand_status = $status;
607        }
608    }
609
610    /**
611     * Get the expand status
612     *
613     * WARNING
614     * NON expandable contents ie PatternLanguage will NEVER return true
615     */
616    public function getExpandStatus() {
617        if ($this->expand_status == null)
618            return false;
619        return $this->expand_status;
620    }
621
622    /** This function will be called by MainUI for each Content BEFORE any getUserUI function is called to allow two pass Content display.
623     * Two pass Content display allows such things as modyfying headers, title, creating content type that accumulate content from other pieces (like RSS feeds)
624     * @return null
625     */
626    public function prepareGetUserUI() {
627        $display_elements = $this->getDisplayElements();
628        foreach ($display_elements as $display_element) {
629            $display_element->prepareGetUserUI();
630        }
631        return parent :: prepareGetUserUI();
632    }
633
634    /** 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.
635     * @param $subclass_admin_interface Html content of the interface element of a children
636     * @param boolean $hide_elements allows the child class ( for example
637     * Pattern Language) to tell the content group not to display elements ) for
638     * elements that need to be hidden before subscription
639     * @return The HTML fragment for this interface */
640    public function getUserUI($subclass_user_interface = null, $hide_elements = false) {
641        $html = '';
642        $html .= "<div class='user_ui_container  " . get_class($this) . "'>\n";
643
644        if ($hide_elements == false) {
645            $display_elements = $this->getDisplayElements();
646            if (count($display_elements) > 0) {
647                foreach ($display_elements as $display_element) {
648                    // If the content group logging is disabled, all the children will inherit this property temporarly
649                    if ($this->getLoggingStatus() == false)
650                        $display_element->setLoggingStatus(false);
651                    $html .= $display_element->getUserUI();
652                }
653            } else {
654                $html .= '<p class="warningmsg">' . _("Sorry, no elements available at this hotspot or all elements of the content group have already been shown") . "</p>\n";
655            }
656        }
657
658        $html .= $subclass_user_interface;
659        $html .= "</div>\n";
660
661        return parent :: getUserUI($html);
662    }
663
664    /**Get all elements
665     * @return an array of ContentGroupElement or an empty arrray */
666    function getElements($additional_where = null) {
667        $db = AbstractDb :: getObject();
668        // Init values
669        $retval = array ();
670        $element_rows = null;
671
672        $sql = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' $additional_where ORDER BY display_order";
673        $db->execSql($sql, $element_rows, false);
674        if ($element_rows != null) {
675            foreach ($element_rows as $element_row) {
676                $element = self :: getObject($element_row['content_group_element_id']);
677                $retval[] = self :: getObject($element_row['content_group_element_id']);
678            }
679        }
680        return $retval;
681    }
682
683    /**
684     * Delete this Content from the database
685     */
686    public function delete(& $errmsg) {
687        if ($this->isPersistent() == false) {
688            foreach ($this->getElements() as $element) {
689                $element->delete($errmsg);
690            }
691        }
692        return parent :: delete($errmsg);
693    }
694
695    /** Reloads the object from the database.  Should normally be called after a set operation.
696    * This function is private because calling it from a subclass will call the
697    * constructor from the wrong scope */
698    private function refresh() {
699        $this->__construct($this->id);
700    }
701
702}
703
704/*
705 * Local variables:
706 * tab-width: 4
707 * c-basic-offset: 4
708 * c-hanging-comment-ender-p: nil
709 * End:
710 */
Note: See TracBrowser for help on using the browser.