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

Revision 1249, 33.0 KB (checked in by benoitg, 6 years ago)

-This is a behemoth "the road to 1.0" commit. I've been working on this for 6 months,
and it's reached the point where others can help. Those are very far reaching changes, please
notify me if anything isn't working right (and im sure I can't have caught everything).

Mostly complete. Missing parts are Content stakeholders, system roles and "su" functionnality.
I need help replacing all the DEPRECATED* methods. Please read the wiki page above for instructions.

  • generic_object_admin.php: More work towards making it generic once again.
  • GenericDataObject?: New class. Eventually, most classes should extend this, instead of directly implementing GenericObject?
  • Menu.php: Finally a uniform Menuing system to replace the mismatch of links that made wifidog impossible to navigate. It's not very sophisticated yet, but it IS permission aware Loosely inspired from Drupal's menuing system. HTML is slightly modified "Son of suckerfish", so will be easy to style (althouh I haven't had time yet). Actual menus are added in the hook_menu methods of each class.
  • VirtualHost.php: Finally properly split off Virtual Hosts, and make Server a singleton.
  • install.php: Do the bare minimum changes so it's still possible to setup a wifidog auth server. However, install.php still needs 1- A good overhaull, 2- A way to install sample databases instead of the minimal one. -Other changes
  • Unbreak signup link for non-javascript enabled devices
  • AbstractDb?: Improve debuging features
  • *:getObject(): Hopefully improve performance of class caching by making sure that we do not manipulate different objects. Otherwise copies would be generated as soon as we change properties, wasting much memory.
    • Add dependency check for XSL module for hotspot_status.php.
    • Some work towards respecting the coding style: http://dev.wifidog.org/wiki/doc/developer/CodingStandard
    • wifidog/admin/index.php: Delete admin page. There is no longuer a concept of a separate admin section. Menu will depend on your access level.
  • 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        'NEVER' => "Content never rotates.  Usefull when showing all elements simultaneously in a specific order."
73    );
74    private $ALLOW_REPEAT_MODES = array (
75        'YES' => "Content can be shown more than once",
76        'NO' => "Content can only be shown once",
77        'ONCE_PER_NODE' => "Content can be shown more than once, but not at the same node"
78    );
79
80    // is_expandable is ONLY for internal use, it use normally only set by the constructor
81    private $is_expandable = true;
82    // this is the actual publicly available status ( so if is_expandable == true it CANNOT be true )
83    private $expand_status = false;
84    private $temporary_display_num_elements;
85    private $display_elements;
86    private $content_selection_mode;
87    private $content_group_row;
88    /** ContentTypeFilter object */
89    protected $allowed_content_types;
90
91    protected function __construct($content_id) {
92
93        $db = AbstractDb :: getObject();
94
95        // Init values
96        $row = null;
97
98        parent :: __construct($content_id);
99
100        $content_id = $db->escapeString($content_id);
101
102        $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'";
103        $db->execSqlUniqueRes($sql, $row, false);
104        if ($row == null) {
105            /*Since the parent Content exists, the necessary data in content_group had not yet been created */
106            $sql = "INSERT INTO content_group (content_group_id) VALUES ('$content_id')";
107            $db->execSqlUpdate($sql, false);
108            $sql = "SELECT * FROM content_group WHERE content_group_id='$content_id'";
109            $db->execSqlUniqueRes($sql, $row, false);
110            if ($row == null) {
111                throw new Exception(_("The content with the following id could not be found in the database: ") . $content_id);
112            }
113
114        }
115
116        $this->content_group_row = $row;
117
118        // These are for internal use only ( private and protected methods ) for dealing with expanding content
119        $this->setTemporaryDisplayNumElements(null);
120    }
121
122    /** Set the allowed content types for the group,
123    * @param $allowed_content_types ContentTypeFilter*/
124    public function setAllowedContentTypes(ContentTypeFilter $allowed_content_types) {
125        $this->allowed_content_types = $allowed_content_types;
126
127    }
128
129    /** In what order is the content displayed to the user
130    * @return string, a key of CONTENT_SELECTION_MODES */
131    public function getContentOrderingMode() {
132        return $this->content_group_row['content_ordering_mode'];
133    }
134
135    /** In what order is the content displayed to the user
136     * @param $content_ordering_mode One of the CONTENT_ORDERING_MODES constants defined in the class
137     * @return true if successfull
138     * */
139    protected function setContentOrderingMode($content_ordering_mode, & $errormsg = null) {
140        $retval = false;
141        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 */ {
142            $db = AbstractDb :: getObject();
143            $content_ordering_mode = $db->escapeString($content_ordering_mode);
144            $db->execSqlUpdate("UPDATE content_group SET content_ordering_mode = '$content_ordering_mode' WHERE content_group_id = '$this->id'", false);
145            $this->refresh();
146            $retval = true;
147        }
148        elseif (!isset ($this->CONTENT_ORDERING_MODES[$content_ordering_mode])) {
149            $errormsg = _("Invalid content selection mode (must be part of CONTENT_ORDERING_MODES)");
150            $retval = false;
151        } else {
152            /* Successfull, but nothing modified */
153            $retval = true;
154        }
155        return $retval;
156    }
157
158    /** When does the content rotate?
159    * @return string, a key of CONTENT_SELECTION_MODES */
160    public function getContentChangesOnMode() {
161        return $this->content_group_row['content_changes_on_mode'];
162    }
163
164    /** When does the content rotate?
165     * @param $content_changes_on_mode One of the content_changes_on_modeS constants defined in the class
166     * @return true if successfull
167     * */
168    protected function setContentChangesOnMode($content_changes_on_mode, & $errormsg = null) {
169        $retval = false;
170        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 */ {
171            $db = AbstractDb :: getObject();
172            $content_changes_on_mode = $db->escapeString($content_changes_on_mode);
173            $db->execSqlUpdate("UPDATE content_group SET content_changes_on_mode = '$content_changes_on_mode' WHERE content_group_id = '$this->id'", false);
174            $this->refresh();
175            $retval = true;
176        }
177        elseif (!isset ($this->CONTENT_CHANGES_ON_MODES[$content_changes_on_mode])) {
178            $errormsg = _("Invalid content selection mode (must be part of CONTENT_CHANGES_ON_MODES)");
179            $retval = false;
180        } else {
181            /* Successfull, but nothing modified */
182            $retval = true;
183        }
184        return $retval;
185    }
186
187    /** Can the same content be shown twice
188     * @return 'YES', 'NO', 'ONCE_PER_NODE' */
189    public function getAllowRepeat() {
190        return $this->content_group_row['allow_repeat'];
191    }
192
193    /** When does the content rotate?
194     * @param $allow_repeat One of the allow_repeatS constants defined in the class
195     * @return true if successfull
196     * */
197    protected function setAllowRepeat($allow_repeat, & $errormsg = null) {
198        $retval = false;
199        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 */ {
200            $db = AbstractDb :: getObject();
201            $allow_repeat = $db->escapeString($allow_repeat);
202            $db->execSqlUpdate("UPDATE content_group SET allow_repeat = '$allow_repeat' WHERE content_group_id = '$this->id'", false);
203            $this->refresh();
204            $retval = true;
205        }
206        elseif (!isset ($this->ALLOW_REPEAT_MODES[$allow_repeat])) {
207            $errormsg = _("Invalid content selection mode (must be part of ALLOW_REPEAT_MODES)");
208            $retval = false;
209        } else {
210            /* Successfull, but nothing modified */
211            $retval = true;
212        }
213        return $retval;
214    }
215
216    /** How many element should be picked for display at once?
217    * @return integer */
218    public function getDisplayNumElements() {
219        if ($this->temporary_display_num_elements == null)
220            return $this->content_group_row['display_num_elements'];
221        else
222            return $this->temporary_display_num_elements;
223    }
224
225    /** How many element should be picked for display at once?
226    * @param $display_num_elements integer, must be greater than zero.
227    * @return true if successfull
228    * */
229    protected function setDisplayNumElements($display_num_elements, & $errormsg = null) {
230        $retval = false;
231        if (($display_num_elements > 0) && $display_num_elements != $this->getDisplayNumElements()) /* Only update database if the mode is valid and there is an actual change */ {
232            $db = AbstractDb :: getObject();
233            $display_num_elements = $db->escapeString($display_num_elements);
234            $db->execSqlUpdate("UPDATE content_group SET display_num_elements = '$display_num_elements' WHERE content_group_id = '$this->id'", false);
235            $this->refresh();
236            $retval = true;
237        }
238        elseif ($display_num_elements <= 0) {
239            $errormsg = _("You must display at least one element");
240            $retval = false;
241        } else {
242            /* Successfull, but nothing modified */
243            $retval = true;
244        }
245        return $retval;
246    }
247
248    /**
249     * This will a temporary limit ( NOT ACTUALLY STORED IN DATABASE )
250     * Use getDisplayNumElements to get the number of elements that can be shown
251     * at once
252     */
253    private function setTemporaryDisplayNumElements($temporary_num_elements) {
254        $this->temporary_display_num_elements = $temporary_num_elements;
255    }
256
257    public function getAdminUI($subclass_admin_interface = null, $title = null) {
258        $html = '';
259        $html .= "<fieldset class='admin_element_group'>\n";
260        $html .= "<legend>" . sprintf(_("%s configuration"), get_class($this)) . "</legend>\n";
261
262        /* content_ordering_mode */
263        $html .= "<li class='admin_element_item_container'>\n";
264        $html .= "<div class='admin_element_label'>" . _("In what order should the content displayed?") . ": </div>\n";
265        $html .= "<div class='admin_element_data'>\n";
266        $name = "content_group_" . $this->id . "_content_ordering_mode";
267        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->CONTENT_ORDERING_MODES, $this->getContentOrderingMode(), $name, null, false);
268        $html .= "</div>\n";
269        $html .= "</li>\n";
270
271        /*content_changes_on_mode */
272        $html .= "<li class='admin_element_item_container'>\n";
273        $html .= "<div class='admin_element_label'>" . _("When does the content rotate?") . ": </div>\n";
274        $html .= "<div class='admin_element_data'>\n";
275        $name = "content_group_" . $this->id . "_content_changes_on_mode";
276        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->CONTENT_CHANGES_ON_MODES, $this->getContentChangesOnMode(), $name, null, false);
277        $html .= "</div>\n";
278        $html .= "</li>\n";
279
280        /* allow_repeat*/
281        $html .= "<li class='admin_element_item_container'>\n";
282        $html .= "<div class='admin_element_label'>" . _("Can content be shown more than once to the same user?") . ": </div>\n";
283        $html .= "<div class='admin_element_data'>\n";
284        $name = "content_group_" . $this->id . "_allow_repeat";
285        $html .= FormSelectGenerator :: generateFromKeyLabelArray($this->ALLOW_REPEAT_MODES, $this->getAllowRepeat(), $name, null, false);
286        $html .= "</div>\n";
287        $html .= "</li>\n";
288
289        /*display_num_elements*/
290        $html .= "<li class='admin_element_item_container'>\n";
291        $html .= "<div class='admin_element_label'>" . ("Pick how many elements for each display?") . ": </div>\n";
292        $html .= "<div class='admin_element_data'>\n";
293        $name = "content_group_" . $this->id . "_display_num_elements";
294        $value = $this->getDisplayNumElements();
295        $html .= "<input type='text' size='2' value='$value' name='$name'>\n";
296        $html .= "</div>\n";
297        $html .= "</li>\n";
298
299        /* Subclass UI */
300        $html .= $subclass_admin_interface;
301
302        $html .= "</fieldset>\n";
303
304        $html .= "<li class='admin_element_item_container'>\n";
305        $html .= "<fieldset class='admin_element_group'>\n";
306        $html .= "<legend>" . sprintf(_("%s display element list"), get_class($this)) . "</legend>\n";
307
308        /* content_group_element*/
309        $name = "content_group_" . $this->id . "_show_expired_elements_request";
310        if (empty ($_REQUEST[$name])) {
311            $showExpired = false;
312            $additionalWhere = "AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n";
313            $title_str = _("Show expired group elements");
314            $html .= "<a name='group_select' onclick=\"document.getElementById('$name').value='" . !$showExpired . "';document.generic_object_form.submit();\">{$title_str}</a>\n";
315
316        } else {
317            $showExpired = true;
318            $additionalWhere = null;
319            $title_str = _("Hide expired group elements");
320            $html .= "<a name='group_select' onclick=\"document.getElementById('$name').value='" . !$showExpired . "';document.generic_object_form.submit();\">{$title_str}</a>\n";
321
322        }
323        $html .= "<input type='hidden' name='$name' id='$name' value='$showExpired'>\n";
324        $name = "content_group_" . $this->id . "_expired_elements_shown";
325        $html .= "<input type='hidden' name='$name' id='$name' value='$showExpired'>\n";
326       
327        $html .= "<ul class='admin_element_list'>\n";
328        foreach ($this->getElements($additionalWhere) as $element) {
329            $html .= "<li class='admin_element_item_container'>\n";
330            $html .= $element->getAdminUI(null, sprintf(_("%s %d"), get_class($element), $element->getDisplayOrder()));
331            $html .= "<div class='admin_element_tools'>\n";
332            $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase";
333            $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s %d"), get_class($element), $element->getDisplayOrder()) . "'>";
334            $html .= "</div>\n";
335            $html .= "</li>\n";
336        }
337        $html .= "<li class='admin_element_item_container'>\n";
338        $html .= self :: getNewContentUI("content_group_{$this->id}_new_element", $this->allowed_content_types);
339        $html .= "</li>\n";
340        $html .= "<li class='admin_element_item_container'>\n";
341        $html .= self :: getSelectExistingContentUI("content_group_{$this->id}_existing_element", "AND content_id != '$this->id' AND is_persistent=TRUE", $this->allowed_content_types);
342        $html .= "</li>\n";
343        $html .= "</ul>\n";
344        $html .= "</fieldset>\n";
345        $html .= "</li>\n";
346        return parent :: getAdminUI($html, $title);
347    }
348
349    function processAdminUI() {
350        // Init values
351        $errmsg = null;
352
353        if ($this->DEPRECATEDisOwner(User :: getCurrentUser()) || User :: getCurrentUser()->DEPRECATEDisSuperAdmin()) {
354            parent :: processAdminUI();
355
356            /* content_ordering_mode */
357            $name = "content_group_" . $this->id . "_content_ordering_mode";
358            $this->setContentOrderingMode(FormSelectGenerator :: getResult($name, null));
359
360            /*content_changes_on_mode */
361            $name = "content_group_" . $this->id . "_content_changes_on_mode";
362            $this->setContentChangesOnMode(FormSelectGenerator :: getResult($name, null));
363
364            /* allow_repeat*/
365            $name = "content_group_" . $this->id . "_allow_repeat";
366            $this->setAllowRepeat(FormSelectGenerator :: getResult($name, null));
367
368            /*display_num_elements*/
369            $name = "content_group_" . $this->id . "_display_num_elements";
370            $this->setDisplayNumElements($_REQUEST[$name]);
371
372            /* content_group_element */
373         $name = "content_group_" . $this->id . "_expired_elements_shown";
374        if (empty ($_REQUEST[$name])) {
375            $additionalWhere = "AND (valid_until_timestamp IS NULL OR valid_until_timestamp >= CURRENT_TIMESTAMP) \n";
376        } else {
377            $additionalWhere = null;
378       }
379            foreach ($this->getElements($additionalWhere) as $element) {
380                $name = "content_group_" . $this->id . "_element_" . $element->GetId() . "_erase";
381                if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
382                    $element->delete($errmsg);
383                } else {
384                    $element->processAdminUI();
385                }
386            }
387
388            // The two following calls will either add a new element or add an existing one ( depending on what button the user clicked
389            /* We explicitely call the ContentGroupElement version of processNewContentUI */
390            ContentGroupElement :: processNewContentUI("content_group_{$this->id}_new_element", $this);
391            // Last parameters allows for existing content ( if any was selected )
392            ContentGroupElement :: processNewContentUI("content_group_{$this->id}_existing_element", $this, true);
393        }
394    }
395
396    /** Is this Content element displayable at this hotspot
397     * @param $node Node, optionnal
398     * @return true or false */
399    public function isDisplayableAt($node) {
400        $old_curent_node = Node :: getCurrentNode();
401        Node :: setCurrentNode($node);
402
403        if (count($this->getDisplayElements()) > 0) {
404            $retval = true;
405        } else {
406            $retval = false;
407        }
408
409        if ($old_curent_node != null) {
410            Node :: setCurrentNode($old_curent_node);
411        }
412
413        return $retval;
414    }
415
416    /**Get the next element or elements to be displayed, depending on the display mode
417    * @return an array of ContentGroupElement or an empty arrray */
418    function getDisplayElements() {
419        //This function is very expensive, cache the results
420        if (!is_array($this->display_elements)) {
421
422            $db = AbstractDb :: getObject();
423
424            // Init values
425            $retval = array ();
426            $user = User :: getCurrentUser();
427            $redisplay_rows = null;
428            $last_order_row = null;
429            $element_rows = null;
430
431            if ($user) {
432                $user_id = $user->getId();
433            } else {
434                $user_id = '';
435            }
436            $node = Node :: getCurrentNode();
437            if ($node) {
438                $node_id = $node->getId();
439            } else {
440                $node_id = '';
441            }
442            $display_num_elements = $this->getDisplayNumElements();
443            /*  'ALWAYS' => "Content always rotates"
444             *  'NEXT_DAY' => "Content rotates once per day"
445             *  'NEXT_LOGIN' => "Content rotates once per session"
446             *  'NEXT_NODE' => "Content rotates each time you change node"
447             *  'NEVER' => "Content never rotates" */
448            $content_changes_on_mode = $this->getContentChangesOnMode();
449
450            $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";
451            /** First, find if we have content to display again because we haven't passed the rotation period */
452            $redisplay_objects = array ();
453            $sql_redisplay = null;
454            if ($content_changes_on_mode != 'ALWAYS' && $content_changes_on_mode != 'NEVER') {
455                $sql_redisplay .= "SELECT content_group_element_id FROM content_group_element \n";
456                $sql_redisplay .= "JOIN content_display_log ON (content_group_element_id=content_id) \n";
457                $sql_redisplay .= " WHERE content_group_id='$this->id' \n";
458                $sql_redisplay .= $sql_time_restrictions;
459
460                if ($content_changes_on_mode == 'NEXT_DAY') {
461                    $sql_redisplay .= "AND date_trunc('day', last_display_timestamp) = date_trunc('day', CURRENT_DATE) \n";
462                }
463                if ($content_changes_on_mode == 'NEXT_LOGIN') {
464                    /**@todo Must fix, this will fail if the user never really connected from a hotspot... */
465                    $sql_redisplay .= "AND last_display_timestamp > (SELECT timestamp_in FROM connections WHERE user_id='$user_id' ORDER BY timestamp_in DESC LIMIT 1) \n";
466                }
467                if ($content_changes_on_mode == 'NEXT_NODE') {
468                    /** We find the close time of the last connection from another node */
469                    $sql_redisplay .= "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";
470                }
471                /* There usually won't be more than one, but if there is, we want the most recents */
472                $sql_redisplay .= " ORDER BY last_display_timestamp DESC ";
473                $db->execSql($sql_redisplay, $redisplay_rows, false);
474                $redisplay_objects = array ();
475                if ($redisplay_rows != null) {
476                    foreach ($redisplay_rows as $redisplay_row) {
477                        $object = self :: getObject($redisplay_row['content_group_element_id']);
478                        if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */
479                            {
480                            $redisplay_objects[] = $object;
481                        }
482                    }
483                }
484                /* Pick the proper number of elements to be re-displayed */
485                $redisplay_objects = array_slice($redisplay_objects, 0, $display_num_elements);
486
487            }
488
489            $new_objects = array ();
490            if (count($redisplay_objects) < $display_num_elements) {
491                /* There aren't enough elements to redisplay, We need new content */
492
493                $sql_base = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' \n";
494                $sql_base .= $sql_time_restrictions;
495                $sql = $sql_base;
496
497                /*'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"*/
498                $allow_repeat = $this->getAllowRepeat();
499                if ($allow_repeat == 'NO') {
500                    $sql_repeat = "AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n";
501                }
502                elseif ($allow_repeat == 'ONCE_PER_NODE') {
503                    $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";
504                } else {
505                    $sql_repeat = null;
506                }
507                $sql .= $sql_repeat;
508                if ($sql_redisplay) {
509                    //We don't want the same content twice...
510                    $sql_repeat_redisplay = " AND content_group_element_id NOT IN ($sql_redisplay) \n";
511                    $sql .= $sql_repeat_redisplay;
512                }
513
514                $content_ordering_mode = $this->getContentOrderingMode();
515                if ($content_ordering_mode == 'SEQUENTIAL') {
516                    $order_by = ' ORDER BY display_order ';
517                    //Find the last content displayed
518                    $sql_last_order = "SELECT display_order FROM content_group_element \n";
519                    $sql_last_order .= "JOIN content_display_log ON (content_group_element_id=content_id) \n";
520                    $sql_last_order .= " WHERE content_group_id='$this->id' \n";
521                    $sql_last_order .= " AND user_id='$user_id' \n";
522
523                    $sql_last_order .= " ORDER BY last_display_timestamp DESC LIMIT 1";
524                    $db->execSqlUniqueRes($sql_last_order, $last_order_row, false);
525                    if ($last_order_row['display_order'] != null) {
526                        $last_order = $last_order_row['display_order'];
527                    } else {
528                        $last_order = 0;
529                    }
530                } else {
531                    $order_by = ' ';
532                    $last_order = 0;
533                }
534                $sql .= $order_by;
535
536                $element_rows = null;
537                if ($content_ordering_mode == 'PSEUDO_RANDOM') {
538                    //Special case, first get only the rows that haven't been displayed before'
539                    $sql_no_repeat = " AND content_group_element_id NOT IN (SELECT content_id FROM content_display_log WHERE user_id = '$user_id') \n";
540                    $db->execSql($sql_base . $sql_no_repeat, $element_rows, false);
541                }
542                //Normal case, or there wasn't any undisplayed content in PSEUDO_RANDOM
543                if ($element_rows == null) {
544                    $db->execSql($sql, $element_rows, false);
545                }
546                if ($element_rows == null) {
547                    $element_rows = array ();
548                }
549                foreach ($element_rows as $element_row) {
550                    $object = self :: getObject($element_row['content_group_element_id']);
551                    if ($object->isDisplayableAt(Node :: GetCurrentNode()) == true) /** Only content available at this hotspot are considered */
552                        {
553                        $new_objects[] = $object;
554                    }
555                }
556
557                if ($content_ordering_mode == 'RANDOM' || $content_ordering_mode == 'PSEUDO_RANDOM') {
558                    shuffle($new_objects);
559                }
560                elseif ($content_ordering_mode == 'SEQUENTIAL') {
561                    foreach ($new_objects as $object) {
562                        if ($object->getDisplayOrder() <= $last_order) {
563                            array_push($new_objects, array_shift($new_objects));
564                            //echo " Pushed ".$object->getDisplayOrder();
565                        }
566                    }
567                }
568
569                /** Pick the proper number of elements */
570                $num_to_pick = $display_num_elements -count($redisplay_objects);
571                $new_objects = array_slice($new_objects, 0, $num_to_pick);
572            }
573           
574            /*echo "<pre>Redisplay: ";
575            print_r($redisplay_objects);
576            echo "New objects: ";
577            print_r($new_objects);
578            echo "</pre>";*/
579           
580            $retval = array_merge($new_objects, $redisplay_objects);
581            //echo count($retval).' returned <br>';
582            $this->display_elements = $retval;
583        }
584        return $this->display_elements;
585    }
586
587    /**
588     * This attribute is for internal use ( to tell if a certain class could be
589     * expanded )
590     * @param $status boolean
591     */
592    protected function setIsExpandable($status) {
593        if (is_bool($status))
594            $this->is_expandable = $status;
595    }
596
597    /**
598     * Tells if this object could be expanded
599     */
600    protected function isExpandable() {
601        return $this->is_expandable;
602    }
603
604    /**
605     * Will expand content ONLY if allowed by isExpandable (which is protected)
606     * @param $status boolean
607     */
608    public function setExpandStatus($status) {
609        if ($this->isExpandable() && is_bool($status)) {
610            //TODO: Try to find a better solution to this problem...
611            if ($status == true)
612                $this->setTemporaryDisplayNumElements(3000);
613            else
614                $this->setTemporaryDisplayNumElements(null);
615            $this->expand_status = $status;
616        }
617    }
618
619    /**
620     * Get the expand status
621     *
622     * WARNING
623     * NON expandable contents ie PatternLanguage will NEVER return true
624     */
625    public function getExpandStatus() {
626        if ($this->expand_status == null)
627            return false;
628        return $this->expand_status;
629    }
630
631    /** This function will be called by MainUI for each Content BEFORE any getUserUI function is called to allow two pass Content display.
632     * Two pass Content display allows such things as modyfying headers, title, creating content type that accumulate content from other pieces (like RSS feeds)
633     * @return null
634     */
635    public function prepareGetUserUI() {
636        $display_elements = $this->getDisplayElements();
637        foreach ($display_elements as $display_element) {
638            $display_element->prepareGetUserUI();
639        }
640        return parent :: prepareGetUserUI();
641    }
642
643    /** 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.
644     * @param $subclass_admin_interface Html content of the interface element of a children
645     * @param boolean $hide_elements allows the child class ( for example
646     * Pattern Language) to tell the content group not to display elements ) for
647     * elements that need to be hidden before subscription
648     * @return The HTML fragment for this interface */
649    public function getUserUI($hide_elements = false) {
650        $html = '';
651        if ($hide_elements == false) {
652            $display_elements = $this->getDisplayElements();
653            if (count($display_elements) > 0) {
654                foreach ($display_elements as $display_element) {
655                    // If the content group logging is disabled, all the children will inherit this property temporarly
656                    if ($this->getLoggingStatus() == false)
657                        $display_element->setLoggingStatus(false);
658                    $html .= $display_element->getUserUI();
659                }
660            } else {
661                //$html .= '<p class="warningmsg">' . _("Sorry, this content-group is empty") . "</p>\n";
662            }
663        }
664                $this->setUserUIMainDisplayContent($html);
665        return parent :: getUserUI();
666    }
667
668    /**Get all elements
669     * @return an array of ContentGroupElement or an empty arrray */
670    function getElements($additional_where = null) {
671        $db = AbstractDb :: getObject();
672        // Init values
673        $retval = array ();
674        $element_rows = null;
675
676        $sql = "SELECT content_group_element_id FROM content_group_element WHERE content_group_id='$this->id' $additional_where ORDER BY display_order";
677        $db->execSql($sql, $element_rows, false);
678        if ($element_rows != null) {
679            foreach ($element_rows as $element_row) {
680                $retval[] = self :: getObject($element_row['content_group_element_id']);
681            }
682        }
683        return $retval;
684    }
685
686    /**
687     * Delete this Content from the database
688     */
689    public function delete(& $errmsg) {
690        if ($this->isPersistent() == false) {
691            foreach ($this->getElements() as $element) {
692                $element->delete($errmsg);
693            }
694        }
695        return parent :: delete($errmsg);
696    }
697
698    /** Reloads the object from the database.  Should normally be called after a set operation.
699    * This function is private because calling it from a subclass will call the
700    * constructor from the wrong scope */
701    private function refresh() {
702        $this->__construct($this->id);
703    }
704
705}
706
707/*
708 * Local variables:
709 * tab-width: 4
710 * c-basic-offset: 4
711 * c-hanging-comment-ender-p: nil
712 * End:
713 */
Note: See TracBrowser for help on using the browser.