root/trunk/wifidog-auth/wifidog/classes/Content.php @ 1435

Revision 1435, 86.2 KB (checked in by gbastien, 3 years ago)

Merged branch 'gbastien' for node group with the trunk:
* Added the concept of node group and hierarchy (in a not too clean way to start with, I will refactor the code before adding new functionalities to nodes and groups) (#246)
* Login and signup and logout script now receive the mac address as parameter (#675)
* Initial abuse control verification now made with the mac address (#676)

  • 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 <benoitg@coeus.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 required classes
48 */
49require_once ('classes/FormSelectGenerator.php');
50require_once ('classes/GenericObject.php');
51require_once ('classes/Cache.php');
52require_once ('classes/HyperLinkUtils.php');
53
54/**
55 * Defines any type of content
56 *
57 * @package    WiFiDogAuthServer
58 * @subpackage ContentClasses
59 * @author     Benoit Grégoire <benoitg@coeus.ca>
60 * @copyright  2005-2006 Benoit Grégoire, Technologies Coeus inc.
61 */
62class Content implements GenericObject {
63    /** Object cache for the object factory (getObject())*/
64    private static $instanceArray = array ();
65    /**
66     * Id of content
67     *
68     * @var string     */
69    protected $id;
70
71    /**
72     * Array containg content from database
73     *
74     * @var array     */
75    protected $content_row;
76
77    /**
78     * Array containg the key-value pairs (KVP) for this content instance
79     *
80     * @var array     */
81    protected $kvps;
82    /**
83     * Type of content
84     *
85     * @var string
86     */
87    private $content_type;
88
89    /**
90     * Defines if logging is enabled or not
91     *
92     * @var bool
93     */
94    private $is_logging_enabled;
95
96    /** Log as part of this other content */
97    private $log_as_content;
98
99    /** The html to be shown in the interaction area of the content */
100    private $user_ui_interaction_area;
101
102    /** The html to be shown in the main display area of the content */
103    private $user_ui_main_content;
104    /**
105     * Constructor
106     *
107     * @param string $content_id Id of content
108     *
109     * @return void
110     */
111    protected function __construct($content_id) {
112        //echo "Content::__construct($content_id)<br/>\n";
113        $db = AbstractDb :: getObject();
114
115        // Init values
116        $row = null;
117
118        // Get content from database
119        $content_id = $db->escapeString($content_id);
120        $sql = "SELECT * FROM content WHERE content_id='$content_id'";
121        $db->execSqlUniqueRes($sql, $row, false);
122
123        if ($row == null) {
124            throw new Exception(_("The content with the following id could not be found in the database: ") . $content_id);
125        }
126
127        $this->content_row = $row;
128        $this->id = $row['content_id'];
129        $this->content_type = $row['content_type'];
130        $kvp_rows = null;
131        $sql = "SELECT key, value FROM content_key_value_pairs WHERE content_id='$content_id'";
132        $db->execSql($sql, $kvp_rows, false);
133        if ($kvp_rows) {
134            foreach ($kvp_rows as $kvp_row) {
135                $this->kvps[$kvp_row['key']] = $kvp_row['value'];
136            }
137        }
138        // By default content display logging is enabled
139        $this->setLoggingStatus(true);
140        $this->log_as_content = & $this;
141    }
142
143    /**
144     * A short string representation of the content
145     *
146     * @return string String representation of the content
147     */
148    public function __toString() {
149        if (empty ($this->content_row['title'])) {
150            $string = _("Untitled content");
151        } else {
152            $title = self :: getObject($this->content_row['title']);
153            $string = $title->__toString();
154        }
155
156        return $string;
157    }
158
159    /**
160     * Create a new Content object in the database
161     *
162     * @param string $content_type The content type to be given to the new object
163     * @param string $id           The id to be given to the new Content. If
164     *                             null, a new id will be assigned
165     *
166     * @return object The newly created Content object, or null if there was an
167     *                error (an exception is also trown)
168
169     */
170    public static function createNewObject($content_type = "Content", $id = null) {
171
172        $db = AbstractDb :: getObject();
173
174        if (empty ($id)) {
175            $contentId = get_guid();
176        } else {
177            $contentId = $db->escapeString($id);
178        }
179
180        if (empty ($content_type)) {
181            throw new Exception(_('Content type is optionnal, but cannot be empty!'));
182        } else {
183            $content_type = $db->escapeString($content_type);
184        }
185
186        $sql = "INSERT INTO content (content_id, content_type) VALUES ('$contentId', '$content_type')";
187
188        if (!$db->execSqlUpdate($sql, false)) {
189            throw new Exception(_('Unable to insert new content into database!'));
190        }
191
192        $object = self :: getObject($contentId);
193
194        // At least add the current user as the default owner
195        $object->AddOwner(User :: getCurrentUser());
196
197        // By default, make it persistent
198        $object->setIsPersistent(true);
199
200        return $object;
201    }
202
203    /**
204     * Get an interface to create a new object.
205     *
206     * @return string HTML markup
207     */
208    public static function getCreateNewObjectUI() {
209        // Init values
210        $html = "";
211        $i = 0;
212        $tab = array ();
213
214        foreach (self :: getAvailableContentTypes() as $className) {
215            $tab[$i][0] = $className;
216            $tab[$i][1] = $className;
217            $i++;
218        }
219
220        if (empty ($tab)) {
221            $html .= _("It appears that you have not installed any Content plugin !");
222        } else {
223            $html .= _("You must select a content type: ");
224            $html .= FormSelectGenerator :: generateFromArray($tab, "TrivialLangstring", "new_content_content_type", "Content", false);
225        }
226
227        return $html;
228    }
229
230    /**
231     * Process the new object interface
232     *
233     * Will return the new object if the user has the credentials
234     * necessary (else an exception is thrown) and if the form was fully
235     * filled (else the object returns null).
236     *
237     * @return object The node object or null if no new node was created
238     */
239    public static function processCreateNewObjectUI() {
240        // Init values
241        $retVal = null;
242
243        $contentType = FormSelectGenerator :: getResult("new_content_content_type", "Content");
244
245        if ($contentType) {
246            $retVal = self :: createNewObject($contentType);
247        }
248
249        return $retVal;
250    }
251
252    /**
253     * Get the content object, specific to it's content type
254     *
255     * @param string $content_id The content Id
256     *
257     * @return object The Content object, or null if there was an error
258     *                (an exception is also thrown)
259
260     */
261    public static function &getObject($content_id) {
262        //echo "Content::g e tObject(".$content_id.")<br/>\n";
263        if (!isset (self :: $instanceArray[$content_id])) {
264            //echo "Cache MISS!<br/>\n";
265            $db = AbstractDb :: getObject();
266
267            // Init values
268            $row = null;
269
270            $content_id = $db->escapeString($content_id);
271            $sql = "SELECT content_type FROM content WHERE content_id='$content_id'";
272            $db->execSqlUniqueRes($sql, $row, false);
273
274            if ($row == null) {
275                throw new Exception(_("The content with the following id could not be found in the database: ") . $content_id);
276            }
277            if (!class_exists($row['content_type'])) {
278                //throw new Exception(_("The following content type isn't valid: ").$row['content_type']);
279                $object = null;
280            } else {
281                self :: $instanceArray[$content_id] = new $row['content_type'] ($content_id);
282                $object = self :: $instanceArray[$content_id];
283            }
284        } else {
285            //echo "Cache HIT!<br/>\n";
286            $object = self :: $instanceArray[$content_id];
287        }
288        return $object;
289
290    }
291
292    /**
293     * Get the list of available content type on the system
294     *
295     * @return array An array of class names
296     */
297    public static function getAvailableContentTypes() {
298        // Init values
299        $contentTypes = array ();
300        $useCache = false;
301        $cachedData = null;
302
303        // Create new cache object with a lifetime of one week
304        $cache = new Cache("ContentClasses", "ClassFileCaches", 604800);
305
306        // Check if caching has been enabled.
307        if ($cache->isCachingEnabled) {
308            $cachedData = $cache->getCachedData("mixed");
309
310            if ($cachedData) {
311                // Return cached data.
312                $useCache = true;
313                $contentTypes = $cachedData;
314            }
315        }
316
317        if (!$useCache) {
318            $dir = WIFIDOG_ABS_FILE_PATH . "classes/Content";
319            $dirHandle = @ opendir($dir);
320
321            if ($dirHandle) {
322                // Loop over the directory
323                while (false !== ($subDir = readdir($dirHandle))) {
324                    // Loop through sub-directories of Content
325                    if ($subDir != '.' && $subDir != '..' && is_dir("{$dir}/{$subDir}")) {
326                        // Only add directories containing corresponding initial Content class
327                        if (is_file("{$dir}/{$subDir}/{$subDir}.php")) {
328                            $contentTypes[] = $subDir;
329                        }
330                    }
331                }
332
333                closedir($dirHandle);
334            } else {
335                throw new Exception(_('Unable to open directory ') . $dir);
336            }
337
338            // Cleanup PHP file extensions and sort the result array
339            $contentTypes = str_ireplace('.php', '', $contentTypes);
340            sort($contentTypes);
341
342            // Check if caching has been enabled.
343            if ($cache->isCachingEnabled) {
344                // Save results into cache, because it wasn't saved into cache before.
345                $cache->saveCachedData($contentTypes, "mixed");
346            }
347        }
348
349        return $contentTypes;
350    }
351    /**
352     * Check if this specific ContentType is usable (all Dependency
353     * met,etc.
354     * This method is meant to be overloaded by the different content classes
355     * @return true or flase
356     */
357    public static function isContentTypeFunctional() {
358        return true;
359    }
360
361    /**
362     * Check if the ContentType is available on the system     *
363     * @param string $classname The classname to check
364     * @return true or flase
365     */
366    public static function isContentTypeAvailable($classname) {
367        if (false === array_search($classname, Content :: getAvailableContentTypes(), true)) {
368            //throw new Exception(_("The following content type isn't valid: ").$contentType);
369            return false;
370        } else {
371            return call_user_func(array($classname, 'isContentTypeFunctional'));
372        }
373    }
374
375    /**
376     * Check if this class is a class or subclass of one of the content types given as parameter
377     * *
378     * @param array $candidates The classnames to check
379     * @return true or flase
380     */
381    public static function isContentType($candidates, $classname) {
382        $retval = false;
383        if (false === is_array($candidates)) {
384            throw new exception("classnames must be an array");
385        }
386        $classname_reflector = new ReflectionClass($classname);
387
388        foreach ($candidates as $candidate) {
389            $candidate_reflector = new ReflectionClass($candidate);
390            //echo"classname: $classname, candidate: $candidate<br>";
391            if ($candidate == $classname || $classname_reflector->isSubclassOf($candidate_reflector)) {
392                //As of PHP5.2, it would apear that isSubclass means a strict sublclass, so the first check was added.
393                //The content meets the criteria
394                //echo "TRUE<br>";
395                $retval = true;
396                break;
397            }
398        }
399        return $retval;
400    }
401
402    /**
403     * Check if this class is a class or subclass of one of the content types given as parameter
404     * *
405     * @param array $candidates The classnames to check
406     * @return true or flase
407     */
408    public static function isExactContentType($candidates, $classname) {
409        $retval = false;
410        if (false === is_array($candidates)) {
411            throw new exception("classnames must be an array");
412        }
413        //$classname_reflector = new ReflectionClass($classname);
414
415        foreach ($candidates as $candidate) {
416            //$candidate_reflector = new ReflectionClass($candidate);
417            //echo"classname: $classname, candidate: $candidate<br>";
418            if ($candidate == $classname) {
419                $retval = true;
420                break;
421            }
422        }
423        return $retval;
424    }
425
426    /**
427     * Check if this class is NOT any of the class or subclass of one of the content types given as parameter
428     * It's the opposite of isContentType()
429     *
430     * @param array $candidates The classnames to check
431     * @return true or flase
432     */
433    public static function isNotContentType($candidates, $classname) {
434        return !self :: isContentType($candidates, $classname);
435    }
436    /**
437     * Get all content
438     *
439     * Can be restricted to a given content type
440     *
441     * @param string $content_type Type of content
442     *
443     * @return mixed Requested content
444     */
445    public static function getAllContent($content_type = "") {
446
447        $db = AbstractDb :: getObject();
448
449        // Init values
450        $whereClause = "";
451        $rows = null;
452        $objects = array ();
453
454        if (!empty ($content_type)) {
455            $content_type = $db->escapeString($content_type);
456            $whereClause = "WHERE content_type = '$content_type'";
457        }
458
459        $db->execSql("SELECT content_id FROM content $whereClause", $rows, false);
460
461        if ($rows) {
462            foreach ($rows as $row) {
463                $objects[] = self :: getObject($row['content_id']);
464            }
465        }
466
467        return $objects;
468    }
469
470    /**
471     * This method contains the interface to add an additional element to a
472     * content object.  (For example, a new string in a Langstring)
473     * It is called when getNewContentUI has only a single possible object type.
474     * It may also be called by the object getAdminUI to avoid code duplication.
475     *
476     * @param string $contentId      The id of the (possibly not yet created) content object.
477     *
478     * @param string $userData=null Array of contextual data optionally sent by displayAdminUI(),
479     *  and only understood by the class (or subclasses) where getNewUI() is defined.
480     *  The function must still function if none of it is present.
481     *
482     * This function understands:
483     *  $userData['contentTypeFilter']
484     *  $userData['calledFromBaseClassNewUI']
485     * @return HTML markup or false.  False means that this object does not support this interface.
486     */
487    public static function getNewUI($contentId, $userData=null) {
488        $db = AbstractDb :: getObject();
489        //echo "Content::getNewUI($contentId,$userData)<br/>\n";
490
491        if(!empty($userData['calledFromBaseClassNewUI'])) {
492            //Break recursion if the subclass doesn't overload this method.
493            return false;
494        }
495
496        // Init values
497        $html = "";
498        $getNewUIData = null;
499        $availableContentTypes = self :: getAvailableContentTypes();
500
501        //echo "Content::getNewUI: userData";pretty_print_r($userData);
502        !empty($userData['contentTypeFilter'])?$contentTypeFilter=$userData['contentTypeFilter']:$contentTypeFilter=null;
503        //echo "Content::getNewUI: contentTypeFilter";pretty_print_r($contentTypeFilter);
504        if (!$contentTypeFilter) {
505            //echo "Get an empty filter";
506            $contentTypeFilter = ContentTypeFilter :: getObject(array ());
507        }
508        //pretty_print_r($content_type_filter);
509        $i = 0;
510        $tab = array ();
511        foreach ($availableContentTypes as $className) {
512            if ($contentTypeFilter->isAcceptableContentClass($className)) {
513                $tab[$i][0] = $className;
514                $tab[$i][1] = $className;
515                $i++;
516            }
517        }
518        $name = "get_new_ui_{$contentId}_content_type";
519        if (count($tab) > 1) {
520            $label = _("Add new Content of type") . ": ";
521            $html .= "<div class='admin_element_data content_add'>";
522            $html .= $label;
523            $html .= FormSelectGenerator :: generateFromArray($tab, 'TrivialLangstring', $name, null, false);
524            $html .= "</div>";
525        } else
526        if (count($tab) == 1) {
527            $html .= '<input type="hidden" name="' . $name . '" value="' . $tab[0][0] . '">';
528            $name = "get_new_ui_{$contentId}_content_type_is_unique";
529            $html .= '<input type="hidden" name="' . $name . '" value="true">';
530            $userData['calledFromBaseClassNewUI']=true;
531            $getNewUIData = call_user_func(array ($tab[0][0], 'getNewUI'), $contentId, $userData);
532
533        } else {
534            throw new Exception(_("No content type matches the filter."));
535        }
536
537        if($getNewUIData != null) {
538            //If the single possible content type given by the filter defined an interface to directly create a new instance
539            $html .= $getNewUIData;
540        }
541        else {
542            if (count($tab) == 1) {
543                $value = sprintf(_("Add a %s"), $tab[0][1]);
544            } else {
545                $value = _("Add");
546            }
547            $html .= "<div class='admin_element_tools'>";
548            $name = "get_new_content_{$contentId}_add";
549            $html .= '<input type="submit" class="submit" name="' . $name . '" value="' . $value . '">';
550            $html .= "</div>";
551        }
552        return $html;
553    }
554
555    /**
556     *
557     *
558     * @param string $contentId  The id of the (possibly not yet created) content object.
559     *
560     * @param string $checkOnly  If true, only check if there is data to be processed.
561     *  Will be used to decide if an object is to be created.  If there is
562     * processNewUI will typically be called again with $chechOnly=false
563     *
564     * @return true if there was data to be processed, false otherwise
565
566     */
567    public static function processNewUI($contentId, $checkOnly=false, $userData=null) {
568        //echo "Content::processNewUI($contentId, $checkOnly)";
569        $retval=false;
570        if(!empty($userData['calledFromBaseClassNewUI'])) {
571            //Break recursion if the subclass doesn't overload this method.
572            return false;
573        }
574        $processNewUIHasData = false;
575        $name = "get_new_ui_{$contentId}_content_type";
576        $contentType = FormSelectGenerator :: getResult($name, null);
577
578        //Was there data to process
579        $name = "get_new_ui_{$contentId}_content_type_is_unique";
580        if(!empty($_REQUEST[$name])){
581            $userData['calledFromBaseClassNewUI']=true;
582            $processNewUIHasData = call_user_func(array ($contentType, 'processNewUI'), $contentId, true, $userData);
583        }
584        //Was add button clicked, or was there data in the new admin UI
585        $name = "get_new_content_{$contentId}_add";
586        if ((!empty($_REQUEST[$name]) && $_REQUEST[$name] == true) || $processNewUIHasData) {
587            $retval=true;
588            if($checkOnly == false) {
589                $object = self::getObject($contentId);
590                $object->setContentType($contentType);
591                if($processNewUIHasData) {
592                    //If there was data to processs, process it for real
593                    call_user_func(array ($contentType, 'processNewUI'), $contentId, false);
594                }
595            }
596        }
597
598        return $retval;
599    }
600
601    /**
602     * Get a flexible interface to generate new content objects
603     *
604     * @param string $user_prefix      A identifier provided by the programmer
605     *                                 to recognise it's generated HTML form
606     * @param string $content_type_filter     If set, the created content must match the filter.  Of only one type matches,
607     * the content will be of this type, otherwise, the user will have
608     *                                 to choose
609     *
610     * @return string HTML markup
611     */
612    public static function getNewContentUI($user_prefix, $content_type_filter = null, $title = null) {
613        //echo "Content::getNewContentUI()";
614        // Init values
615        $html = "";
616        $getNewUIData = null;
617        $html .= "<fieldset class='admin_container Content'>\n";
618        if (!empty ($title)) {
619            $html .= "<legend>$title</legend>\n";
620        }
621        $futureContentId = get_guid();
622        $name = "get_new_content_{$user_prefix}_future_id";
623        $html .= '<input type="hidden" name="' . $name . '" value="' . $futureContentId . '">';
624        $userData['contentTypeFilter']=$content_type_filter;
625
626        $html .= Content::getNewUI($futureContentId, $userData);
627        $html .= "</fieldset>\n";
628        return $html;
629    }
630
631    /**
632     * Get the created content object, IF one was created OR get existing
633     * content (depending on what the user clicked)
634     *
635     * @param string $user_prefix                A identifier provided by the
636     *                                           programmer to recognise it's
637     *                                           generated form
638     * @param bool   $associate_existing_content If true it allows to get
639     *                                           existing object
640     *
641     * @return object The Content object, or null if the user didn't create one
642
643     */
644    public static function processNewContentUI($user_prefix) {
645        //echo "Content::processNewContentUI()";
646        // Init values
647        $object = null;
648        $name = "get_new_content_{$user_prefix}_future_id";
649        $futureContentId = $_REQUEST[$name];
650
651        if(Content::processNewUI($futureContentId, true)) {
652            self :: createNewObject('Content', $futureContentId);//The true content type will be set by processNewUI()
653            //If there was data to processs, process it for real
654            Content::processNewUI($futureContentId, false);
655            $object = self :: getObject($futureContentId);//Content type has changed...
656        }
657        //pretty_print_r($object);
658        return $object;
659    }
660    /**
661     * Get the created content object, IF one was created OR get existing
662     * content (depending on what the user clicked)
663     *
664     * @param string $user_prefix                A identifier provided by the
665     *                                           programmer to recognise it's
666     *                                           generated form
667     * @return object The Content object, or null if the user didn't create one
668
669     */
670    public static function processSelectExistingContentUI($user_prefix) {
671        // Init values
672        $object = null;
673        $name = "{$user_prefix}";
674        /*
675         * The result is a content ID
676         */
677        $contentUiResult = FormSelectGenerator :: getResult($name, null);
678        $name = "{$user_prefix}_add";
679
680        //Was add button clicked, or whare there data in the new admin UI
681        if ((!empty($_REQUEST[$name]) && $_REQUEST[$name] == true)) {
682            $object = self :: getObject($contentUiResult);
683        }
684        return $object;
685    }
686
687    /**
688     * Get a flexible interface to manage content linked to a node, a network
689     * or anything else
690     *
691     * @param string $user_prefix            A identifier provided by the
692     *                                       programmer to recognise it's
693     *                                       generated HTML form
694     * @param string $link_table             Table to link from
695     * @param string $link_table_obj_key_col Column in linked table to match
696     * @param string $link_table_obj_key     Key to be found in linked table
697     * @param string $default_display_page
698     * @param string $default_display_area
699     * @return string HTML markup
700
701     */
702    public static function getLinkedContentUI($user_prefix, $link_table, $link_table_obj_key_col, $link_table_obj_key, $default_display_page = 'portal', $default_display_area = 'main_area_middle') {
703
704        $db = AbstractDb :: getObject();
705
706        // Init values
707        $html = "";
708
709        $link_table = $db->escapeString($link_table);
710        $link_table_obj_key_col = $db->escapeString($link_table_obj_key_col);
711        $link_table_obj_key = $db->escapeString($link_table_obj_key);
712
713        /* Content already linked */
714        $current_content_sql = "SELECT * FROM $link_table WHERE $link_table_obj_key_col='$link_table_obj_key' ORDER BY display_page, display_area, display_order, subscribe_timestamp DESC";
715        $rows = null;
716        $db->execSql($current_content_sql, $rows, false);
717
718        $html .= "<table class='content_management_tools'>\n";
719        $html .= "<th>" . _('Display page') . '</th><th>' . _('Area') . '</th><th>' . _('Order') . '</th><th>' . _('Content') . '</th><th>' . _('Actions') . '</th>' . "\n";
720        if ($rows)
721        foreach ($rows as $row) {
722            $content = self :: getObject($row['content_id']);
723            $html .= "<tr class='already_linked_content'>\n";
724            /* Display page */
725            $name = "{$user_prefix}_" . $content->GetId() . "_display_page";
726            $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_pages', 'display_page', 'display_page', $row['display_page'], $name, null) . "</td>\n";
727            $name = "{$user_prefix}_" . $content->GetId() . "_display_area";
728            $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_areas', 'display_area', 'display_area', $row['display_area'], $name, null) . "</td>\n";
729            $name = "{$user_prefix}_" . $content->GetId() . "_display_order";
730            $html .= "<td><input type='text' name='$name' value='{$row['display_order']}' size=2 class='linked_content_order'></td>\n";
731            $html .= "<td>\n";
732            $html .= $content->getListUI();
733            $html .= "</td>\n";
734            $html .= "<td>\n";
735            $name = "{$user_prefix}_" . $content->GetId() . "_edit";
736            $html .= "<input type='button' class='submit' name='$name' value='" . _("Edit") . "' onClick='window.open(\"" . GENERIC_OBJECT_ADMIN_ABS_HREF . "?object_class=Content&action=edit&object_id=" . $content->GetId() . "\");'>\n";
737            $name = "{$user_prefix}_" . $content->GetId() . "_erase";
738            $html .= "<input type='submit' class='submit' name='$name' value='" . _("Remove") . "'>";
739            $html .= "</td>\n";
740            $html .= "</tr>\n";
741        }
742
743        /* Add existing content */
744        $html .= "<tr class='add_existing_content'>\n";
745        $name = "{$user_prefix}_new_existing_display_page";
746        $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_pages', 'display_page', 'display_page', $default_display_page, $name, null) . "</td>\n";
747        $name = "{$user_prefix}_new_existing_display_area";
748        $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_areas', 'display_area', 'display_area', $default_display_area, $name, null) . "</td>\n";
749        $name = "{$user_prefix}_new_existing_display_order";
750        $html .= "<td><input type='text' name='$name' value='1' size=2 class='linked_content_order'></td>\n";
751        $html .= "<td colspan=2>\n";
752        $name = "{$user_prefix}_new_existing";
753        $contentSelector = Content :: getSelectExistingContentUI($name, "AND is_persistent=TRUE AND content_id NOT IN (SELECT content_id FROM $link_table WHERE $link_table_obj_key_col='$link_table_obj_key')");
754        $html .= $contentSelector;
755        $html .= "</td>\n";
756        $html .= "</tr>\n";
757
758        /* Add new content */
759        $html .= "<tr class='add_new_content'>\n";
760        $name = "{$user_prefix}_new_display_page";
761        $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_pages', 'display_page', 'display_page', $default_display_page, $name, null) . "</td>\n";
762        $name = "{$user_prefix}_new_display_area";
763        $html .= "<td>" . FormSelectGenerator :: generateFromTable('content_available_display_areas', 'display_area', 'display_area', $default_display_area, $name, null) . "</td>\n";
764        $name = "{$user_prefix}_new_display_order";
765        $html .= "<td><input type='text' name='$name' value='1' size=2 class='linked_content_order'></td>\n";
766        $html .= "<td colspan=2>\n";
767        $name = "{$user_prefix}_new";
768        $html .= self :: getNewContentUI($name, $content_type = null);
769        $html .= "</td>\n";
770        $html .= "</table>\n";
771        return $html;
772    }
773
774    /** Get the created Content object, IF one was created
775     * OR Get existing content ( depending on what the user clicked )
776     * @param $user_prefix A identifier provided by the programmer to recognise it's generated form
777     * @param $associate_existing_content boolean if true allows to get existing
778     * object
779     * @return the Content object, or null if the user didn't create one
780     */
781    static function processLinkedContentUI($user_prefix, $link_table, $link_table_obj_key_col, $link_table_obj_key) {
782        $db = AbstractDb :: getObject();
783        $link_table = $db->escapeString($link_table);
784        $link_table_obj_key_col = $db->escapeString($link_table_obj_key_col);
785        $link_table_obj_key = $db->escapeString($link_table_obj_key);
786        /* Content already linked */
787        $current_content_sql = "SELECT * FROM $link_table WHERE $link_table_obj_key_col='$link_table_obj_key'";
788        $rows = null;
789        $db->execSql($current_content_sql, $rows, false);
790     
791        if ($rows)
792        foreach ($rows as $row) {
793            $content = Content :: getObject($row['content_id']);
794            $content_id = $db->escapeString($content->getId());
795            $sql = null;
796            $name = "{$user_prefix}_" . $content->GetId() . "_erase";
797            if (!empty ($_REQUEST[$name])) {
798                $sql .= "DELETE FROM $link_table WHERE $link_table_obj_key_col='$link_table_obj_key' AND content_id = '$content_id';\n";
799
800            } else {
801                /* Display page */
802                $name = "{$user_prefix}_" . $content->GetId() . "_display_page";
803                $new_display_page = FormSelectGenerator :: getResult($name, null);
804                if ($new_display_page != $row['display_page']) {
805                    $new_display_page = $db->escapeString($new_display_page);
806                    $sql .= "UPDATE $link_table SET display_page='$new_display_page' WHERE $link_table_obj_key_col='$link_table_obj_key' AND content_id = '$content_id';\n";
807
808                }
809                /* Display area */
810                $name = "{$user_prefix}_" . $content->GetId() . "_display_area";
811                $new_display_area = FormSelectGenerator :: getResult($name, null);
812                if ($new_display_area != $row['display_area']) {
813                    $new_display_area = $db->escapeString($new_display_area);
814                    $sql .= "UPDATE $link_table SET display_area='$new_display_area' WHERE $link_table_obj_key_col='$link_table_obj_key' AND content_id = '$content_id';\n";
815                }
816                /* Display order */
817                $name = "{$user_prefix}_" . $content->GetId() . "_display_order";
818                if ($_REQUEST[$name] != $row['display_order']) {
819                    $new_display_order = $db->escapeString($_REQUEST[$name]);
820                    $sql .= "UPDATE $link_table SET display_order='$new_display_order' WHERE $link_table_obj_key_col='$link_table_obj_key' AND content_id = '$content_id';\n";
821                }
822            }
823            if ($sql) {
824                $db->execSqlUpdate($sql, false);
825            }
826        }
827        /* Add existing content */
828        $name = "{$user_prefix}_new_existing_add";
829        if (!empty ($_REQUEST[$name])) {
830            $name = "{$user_prefix}_new_existing";
831            $content = Content :: processSelectContentUI($name);
832            if ($content) {
833                /* Display page */
834                $name = "{$user_prefix}_new_existing_display_page";
835                $new_display_page = $db->escapeString(FormSelectGenerator :: getResult($name, null));
836                /* Display area */
837                $name = "{$user_prefix}_new_existing_display_area";
838                $new_display_area = $db->escapeString(FormSelectGenerator :: getResult($name, null));
839                /* Display order */
840                $name = "{$user_prefix}_new_existing_display_order";
841                $new_display_order = $db->escapeString($_REQUEST[$name]);
842                $content_id = $db->escapeString($content->getId());
843                $sql = "INSERT INTO $link_table (content_id, $link_table_obj_key_col, display_page, display_area, display_order) VALUES ('$content_id', '$link_table_obj_key', '$new_display_page', '$new_display_area', $new_display_order);\n";
844                $db->execSqlUpdate($sql, false);
845            }
846        }
847        /* Add new content */
848        $name = "{$user_prefix}_new";
849        $content = self :: processNewContentUI($name);
850        if ($content) {
851            /* Display page */
852            $name = "{$user_prefix}_new_display_page";
853            $new_display_page = $db->escapeString(FormSelectGenerator :: getResult($name, null));
854            /* Display area */
855            $name = "{$user_prefix}_new_display_area";
856            $new_display_area = $db->escapeString(FormSelectGenerator :: getResult($name, null));
857            /* Display order */
858            $name = "{$user_prefix}_new_display_order";
859            $new_display_order = $db->escapeString($_REQUEST[$name]);
860            $content_id = $db->escapeString($content->getId());
861            $sql = "INSERT INTO $link_table (content_id, $link_table_obj_key_col, display_page, display_area, display_order) VALUES ('$content_id', '$link_table_obj_key', '$new_display_page', '$new_display_area', $new_display_order);\n";
862            $db->execSqlUpdate($sql, false);
863        }
864    }
865
866    /**
867     * Get an interface to pick content from all persistent content
868     *
869     * It either returns a select box or an extended table
870     *
871     * @param string $user_prefix             An identifier provided by the
872     *                                        programmer to recognise it's
873     *                                        generated HTML form
874     * @param string $sql_additional_where    Addidional where conditions to
875     *                                        restrict the candidate objects
876     * @param string $content_type_filter     If set, the created content must match the filter.
877     * @param string $order                   Order of output (default: by
878     *                                        creation time)
879     * @param string $type_interface          Type of interface:
880     *                                          - "select": default, shows a
881     *                                            select box
882     *                                          - "table": showsa table with
883     *                                            extended information
884     *
885     * @return string HTML markup
886
887     */
888    public static function getSelectExistingContentUI($user_prefix, $sql_additional_where = null, $content_type_filter = null, $order = "creation_timestamp DESC", $type_interface = "select") {
889
890        $db = AbstractDb :: getObject();
891
892        // Init values
893        $html = '';
894        $retVal = array ();
895        $contentRows = null;
896        if ($content_type_filter == null) {
897            //Get an empty filter
898            $content_type_filter = ContentTypeFilter :: getObject(array ());
899        }
900
901        if (!User :: getCurrentUser()) {
902            throw new Exception(_('Access denied!'));
903        }
904
905        if ($type_interface != "table") {
906            $html .= "<fieldset class='admin_container Content'>\n";
907
908            if (!empty ($title)) {
909                $html .= "<legend>$title</legend>\n";
910            }
911
912            $html .= _("Select from reusable content library") . ": ";
913        }
914
915        $name = "{$user_prefix}";
916
917        $sql = "SELECT * FROM content WHERE 1=1 $sql_additional_where ORDER BY $order";
918
919        $db->execSql($sql, $contentRows, false);
920
921        if ($contentRows != null) {
922            $i = 0;
923
924            if ($type_interface == "table") {
925                $html .= "<table class='content_admin'>\n";
926                $html .= "<tr><th>" . _("Title") . "</th><th>" . _("Content type") . "</th><th>" . _("Description") . "</th><th></th></tr>\n";
927            }
928
929            foreach ($contentRows as $contentRow) {
930                $content = Content :: getObject($contentRow['content_id']);
931                //echo get_class($content)." ".$contentRow['content_id']."<br>";
932                if ($content && $content_type_filter->isAcceptableContentClass(get_class($content))) {
933                    if ($type_interface != "table") {
934                        $tab[$i][0] = $content->getId();
935                        $tab[$i][1] = $content->__toString() . " (" . get_class($content) . ")";
936                        $i++;
937                    } else {
938                        if (!empty ($contentRow['title'])) {
939                            $title = Content :: getObject($contentRow['title']);
940                            $titleUI = $title->__toString();
941                        } else {
942                            $titleUI = "";
943                        }
944
945                        if (!empty ($contentRow['description'])) {
946                            $description = Content :: getObject($contentRow['description']);
947                            $descriptionUI = $description->__toString();
948                        } else {
949                            $descriptionUI = "";
950                        }
951
952                        $href = GENERIC_OBJECT_ADMIN_ABS_HREF . "?object_id={$contentRow['content_id']}&object_class=Content&action=edit";
953                        $html .= "<tr><td>$titleUI</td><td><a href='$href'>{$contentRow['content_type']}</a></td><td>$descriptionUI</td>\n";
954
955                        $href = GENERIC_OBJECT_ADMIN_ABS_HREF . "?object_id={$contentRow['content_id']}&object_class=Content&action=delete";
956                        $html .= "<td><a href='$href'>" . _("Delete") . "</a></td>";
957
958                        $html .= "</tr>\n";
959                    }
960                }
961            }
962
963            if ($type_interface != "table") {
964                if (isset ($tab)) {
965                    $html .= FormSelectGenerator :: generateFromArray($tab, null, $name, null, false, null, null, 40);
966                    //DEBUG!! get_existing_content_
967                    $name = "{$user_prefix}_add";
968                    $value = _("Add");
969                    $html .= "<div class='admin_element_tools'>";
970                    $html .= '<input type="submit" class="submit" name="' . $name . '" value="' . $value . '">';
971                    $html .= "</div>";
972                } else {
973                    $html .= "<div class='warningmsg'>" . _("Sorry, no elligible content available in the database") . "</div>\n";
974                }
975                $html .= "</fieldset>\n";
976            } else {
977                $html .= "</table>\n";
978            }
979        } else {
980            $html .= "<div class='warningmsg'>" . _("Sorry, no elligible content available in the database") . "</div>\n";
981        }
982
983        return $html;
984    }
985
986    /** Get the selected Content object.
987     * @param $user_prefix A identifier provided by the programmer to recognise it's generated form
988     * @return the Content object
989     */
990    static function processSelectContentUI($user_prefix) {
991        $name = "{$user_prefix}";
992        if (!empty ($_REQUEST[$name]))
993        return Content :: getObject($_REQUEST[$name]);
994        else
995        return null;
996    }
997
998    /** Get the true object type represented by this isntance
999     * @return an array of class names */
1000    public function getObjectType() {
1001        return $this->content_type;
1002    }
1003
1004    /**
1005     * Key-value pairs are an easy way to extend Content types
1006     * without having to needlessly modify the wifidog schema.
1007     * They are appropriate when  you content subtype needs to
1008     * store simple type that fit the key-value model.  (that is
1009     * onke key->single value for a given Content instance.
1010     * @throws exception if key cannot be found
1011     * @param $key The key whose value is to be retrieved.Keys
1012     * must also be unique fo the entire object inheritance tree.
1013     * Because of this, key naming convention is as follows:
1014     * ClassName_key_name
1015     * @return The value of the pair.  To check if a key exists,
1016     * check === null (and not == null)
1017
1018     */
1019    protected function getKVP($key) {
1020        if (isset ($this->kvps[$key])) {
1021            return $this->kvps[$key];
1022        } else {
1023            //throw new exception (sprintf(_("Key %s does not exist"), $key));
1024            return null;
1025        }
1026    }
1027
1028    /**
1029     * Key-value pairs are an easy way to extend Content types
1030     * without having to needlessly modify the wifidog schema.
1031     * They are appropriate when  you content subtype needs to
1032     * store simple type that fit the key-value model.  (that is
1033     * onke key->single value for a given Content instance.
1034     * @throws exception if key cannot be found
1035     * @param $key The key whose value is to be retrieved.  Keys
1036     * must also be unique fo the entire object inheritance tree.
1037     * Because of this, key naming convention is as follows:
1038     * ClassName_key_name
1039     * @param $value The value of the key.  Any value representable as a string.  Setting it to null will delete the key.
1040     * @return The value of the pair
1041     */
1042    protected function setKVP($key, $value) {
1043        $retval = true;
1044        $db = AbstractDb :: getObject();
1045        $value_sql = $db->escapeString($value);
1046        $key_sql = $db->escapeString($key);
1047        //pretty_print_r($this->kvps);
1048        if($key==null) {
1049            throw new Exception (_("KVP key cannot be null"));
1050        } else if($value===null) {
1051            //Delete the KVP
1052            $retval = $db->execSqlUpdate("DELETE FROM content_key_value_pairs WHERE content_id='" . $this->getId() . "' AND key='$key_sql'", false);
1053            if(isset ($this->kvps[$key])) {
1054                unset ($this->kvps[$key]);
1055            }
1056        } else if (!isset ($this->kvps[$key])) {
1057            //This is a new key
1058            $retval = $db->execSqlUpdate("INSERT INTO content_key_value_pairs (content_id, key, value) VALUES ('" . $this->getId() . "', '$key_sql', '$value_sql')", false);
1059        } else
1060        if ($this->kvps[$key] != $value) {
1061            //This is an existing key, and it's been modified
1062            $retval = $db->execSqlUpdate("UPDATE content_key_value_pairs SET value ='" . $value_sql . "' WHERE content_id='" . $this->getId() . "' AND key='$key_sql'", false);
1063        }
1064        $this->refresh();
1065        return $retval;
1066    }
1067
1068    /**
1069     * Get content title
1070     * @return content a content sub-class
1071     */
1072    public function getTitle() {
1073        $retval = null;
1074        if(!empty($this->content_row['title'])){
1075            $retval = self :: getObject($this->content_row['title']);
1076        }
1077        return $retval;
1078    }
1079
1080    /**
1081     * Get content description
1082     * @return content a content sub-class
1083     */
1084    public function getDescription() {
1085        $retval = null;
1086        if(!empty($this->content_row['description'])){
1087            $retval = self :: getObject($this->content_row['description']);
1088        }
1089        return $retval;
1090    }
1091
1092    /**
1093     * Get content long description
1094     * @return content a content sub-class
1095     */
1096    public function getLongDescription() {
1097        $retval = null;
1098        if(!empty($this->content_row['long_description'])){
1099            $retval = self :: getObject($this->content_row['long_description']);
1100        }
1101        return $retval;
1102    }
1103
1104    /**
1105     * Get content project info
1106     * @return content a content sub-class
1107     */
1108    public function getProjectInfo() {
1109        $retval = null;
1110        if(!empty($this->content_row['project_info'])){
1111            $retval = self :: getObject($this->content_row['project_info']);
1112        }
1113        return $retval;
1114    }
1115
1116    /** Set the object type of this object
1117     * Note that after using this, the object must be re-instanciated to have the right type
1118     * */
1119    private function setContentType($content_type) {
1120        $db = AbstractDb :: getObject();
1121        $content_type = $db->escapeString($content_type);
1122        if (!self :: isContentTypeAvailable($content_type)) {
1123            throw new Exception(_("The following content type isn't valid: ") . $content_type);
1124        }
1125        $sql = "UPDATE content SET content_type = '$content_type' WHERE content_id='$this->id'";
1126
1127        if (!$db->execSqlUpdate($sql, false)) {
1128            throw new Exception(_("Update was unsuccessfull (database error)"));
1129        }
1130        unset(self :: $instanceArray[$this->id]);//Clear the cache or we will have problems even if we re-instanciate.
1131    }
1132
1133    /** Check if a user is one of the owners of the object
1134     * @param $user The user to be added to the owners list
1135     * @param $is_author Optionnal, true or false.  Set to true if the user is one of the actual authors of the Content
1136     * @return true on success, false on failure */
1137    public function addOwner(User $user, $is_author = false) {
1138        $db = AbstractDb :: getObject();
1139        $content_id = "'" . $this->id . "'";
1140        $user_id = "'" . $db->escapeString($user->getId()) . "'";
1141        $is_author ? $is_author = 'TRUE' : $is_author = 'FALSE';
1142        $sql = "INSERT INTO content_has_owners (content_id, user_id, is_author) VALUES ($content_id, $user_id, $is_author)";
1143
1144        if (!$db->execSqlUpdate($sql, false)) {
1145            throw new Exception(_('Unable to insert the new Owner into database.'));
1146        }
1147
1148        return true;
1149    }
1150
1151    /** Remove an owner of the content
1152     * @param $user The user to be removed from the owners list
1153     */
1154    public function deleteOwner(User $user, $is_author = false) {
1155        $db = AbstractDb :: getObject();
1156        $content_id = "'" . $this->id . "'";
1157        $user_id = "'" . $db->escapeString($user->getId()) . "'";
1158
1159        $sql = "DELETE FROM content_has_owners WHERE content_id=$content_id AND user_id=$user_id";
1160
1161        if (!$db->execSqlUpdate($sql, false)) {
1162            throw new Exception(_('Unable to remove the owner from the database.'));
1163        }
1164
1165        return true;
1166    }
1167
1168    /**
1169     * Indicates display logging status
1170     */
1171    public function getLoggingStatus() {
1172        return $this->is_logging_enabled;
1173    }
1174
1175    /**
1176     * Sets display logging status
1177     */
1178    public function setLoggingStatus($status) {
1179        if (is_bool($status))
1180        $this->is_logging_enabled = $status;
1181    }
1182
1183    /** Get the PHP timestamp of the last time this content was displayed
1184     * @param $user User, Optional, if present, restrict to the selected user
1185     * @param $node Node, Optional, if present, restrict to the selected node
1186     * @return PHP timestamp (seconds since UNIX epoch) if the content has been
1187     * displayed before, an empty string otherwise.
1188     */
1189    public function getLastDisplayTimestamp($user = null, $node = null) {
1190        $db = AbstractDb :: getObject();
1191        $retval = '';
1192        $sql = "SELECT EXTRACT(EPOCH FROM last_display_timestamp) as last_display_unix_timestamp FROM content_display_log WHERE content_id='{$this->id}' \n";
1193
1194        if ($user) {
1195            $user_id = $db->escapeString($user->getId());
1196            $sql .= " AND user_id = '{$user_id}' \n";
1197        }
1198        if ($node) {
1199            $node_id = $db->escapeString($node->getId());
1200            $sql .= " AND node_id = '{$node_id}' \n";
1201        }
1202        $sql .= " ORDER BY last_display_timestamp DESC ";
1203        $db->execSql($sql, $log_rows, false);
1204        if ($log_rows) {
1205            $retval = $log_rows[0]['last_display_unix_timestamp'];
1206        }
1207
1208        return $retval;
1209    }
1210
1211    /** Is this Content element displayable at this hotspot, many classes override this
1212     * @param $node Node, optionnal
1213     * @return true or false */
1214    public function isDisplayableAt($node) {
1215        return true;
1216    }
1217
1218    /** Check if a user is one of the owners of the object
1219     * @param $user User object:  the user to be tested.
1220     * @return true if the user is a owner, false if he isn't of the user is null */
1221    public function DEPRECATEDisOwner($user) {
1222        $db = AbstractDb :: getObject();
1223        $retval = false;
1224        if ($user != null) {
1225            $user_id = $db->escapeString($user->GetId());
1226            $sql = "SELECT * FROM content_has_owners WHERE content_id='$this->id' AND user_id='$user_id'";
1227            $db->execSqlUniqueRes($sql, $content_owner_row, false);
1228            if ($content_owner_row != null) {
1229                $retval = true;
1230            }
1231        }
1232
1233        return $retval;
1234    }
1235
1236    /** Get the authors of the Content
1237     * @return null or array of User objects */
1238    public function getAuthors() {
1239        $db = AbstractDb :: getObject();
1240        $retval = array ();
1241        $content_owner_row = null;
1242        $sql = "SELECT user_id FROM content_has_owners WHERE content_id='$this->id' AND is_author=TRUE";
1243        $db->execSqlUniqueRes($sql, $content_owner_row, false);
1244        if ($content_owner_row != null) {
1245            $user = User :: getObject($content_owner_row['user_id']);
1246            $retval[] = $user;
1247        }
1248
1249        return $retval;
1250    }
1251
1252    /** Get the owners of the Content
1253     * @return null or array of User objects */
1254    public function getOwners() {
1255        $db = AbstractDb :: getObject();
1256        $retval = array ();
1257        $content_owner_row = null;
1258        $sql = "SELECT user_id FROM content_has_owners WHERE content_id='$this->id'";
1259        $db->execSqlUniqueRes($sql, $content_owner_row, false);
1260        if ($content_owner_row != null) {
1261            $user = User :: getObject($content_owner_row['user_id']);
1262            $retval[] = $user;
1263        }
1264
1265        return $retval;
1266    }
1267
1268    /** @see GenricObject
1269     * @return The id */
1270    public function getId() {
1271        return $this->id;
1272    }
1273
1274    /** When a content object is set as Simple, it means that is is used merely to contain it's own data.  No title, description or other metadata will be set or displayed, during display or administration
1275     * @return true or false */
1276    public function isSimpleContent() {
1277        return false;
1278    }
1279
1280    /** Indicate that the content is suitable to store plain text.
1281     * @return true or false */
1282    public function isTextualContent() {
1283        return false;
1284    }
1285
1286    /** This function will be called by MainUI for each Content BEFORE any getUserUI function is called to allow two pass Content display.
1287     * Two pass Content display allows such things as modyfying headers, title, creating content type that accumulate content from other pieces (like RSS feeds)
1288     * @return null
1289     */
1290    public function prepareGetUserUI() {
1291        return null;
1292    }
1293
1294    /** Does the content have any displayable metadata?
1295     * @return true or false */
1296    protected function hasDisplayableMetadata() {
1297        $retval = false;
1298        $metadata = $this->getTitle();
1299        if ($metadata && $this->titleShouldDisplay()){
1300            $retval = true;
1301        }
1302        elseif ($this->getDescription()){
1303            $retval = true;
1304        }
1305        elseif ($this->getLongDescription()){
1306            $retval = true;
1307        }
1308        elseif ($this->getProjectInfo()){
1309            $retval = true;
1310        }
1311        elseif($this->getAuthors()) {
1312            $retval = true;
1313        }
1314        return $retval;
1315    }
1316
1317    /** Set the content to be displayed in the main display area.  Needs to be set by subclasses
1318     * @param $html:  Html markup for the displayed content */
1319    protected function setUserUIMainDisplayContent($html) {
1320        $this->user_ui_main_content = $html;
1321    }
1322
1323    /** Set the content to be displayed in the interactive display area.  Needs to be set by subclasses
1324     * @param $html:  Html markup for the displayed content */
1325    protected function setUserUIMainInteractionArea($html) {
1326        $this->user_ui_interaction_area = $html;
1327    }
1328    /** Retreives the user interface of this object.  Anything that overrides this method should use
1329     * setUserUIMainDisplayContent() and/or setUserUIMainInteractionArea before returning it's parent's
1330     * getUserUI() return value at the end of processing.
1331
1332     *      * @return The HTML fragment for this interface */
1333    public function getUserUI($subclass_user_interface = null) {
1334        $html = '';
1335        $hasDisplayableMetadata = $this->hasDisplayableMetadata();
1336        $hasDisplayableMetadata ? $hasDisplayableMetadataClass = 'hasDisplayableMetadata' : $hasDisplayableMetadataClass = null;
1337        $html .= "<div class='user_ui_main_outer " . get_class($this) . " $hasDisplayableMetadataClass'>\n";
1338        $html .= "<div class='user_ui_main_inner'>\n";
1339        if (!empty ($this->content_row['title']) && $this->titleShouldDisplay()) {
1340            $html .= "<div class='user_ui_title'>\n";
1341            $title = self :: getObject($this->content_row['title']);
1342            $title->setLogAsContent($this);
1343            // If the content logging is disabled, all the children will inherit this property temporarly
1344            if ($this->getLoggingStatus() == false)
1345            $title->setLoggingStatus(false);
1346            $html .= $title->getUserUI();
1347            $html .= "</div>\n";
1348        }
1349        if($this->user_ui_main_content) {
1350            if (!$hasDisplayableMetadata) {
1351                $html .= "\n<div class='user_ui_main_content'>$this->user_ui_main_content</div>\n";
1352            } else {
1353                $html .= "<table><tr>\n";
1354                $html .= "<td  class='user_ui_main_content'>\n$this->user_ui_main_content</td>\n";
1355                $html .= "<td>\n";
1356            }
1357        }
1358        $authors = $this->getAuthors();
1359        if (count($authors) > 0) {
1360            $html .= "<div class='user_ui_authors'>\n";
1361            $html .= _("Author(s):");
1362            foreach ($authors as $user) {
1363                $html .= $user->getListUI() . " ";
1364            }
1365            $html .= "</div>\n";
1366        }
1367
1368        if (!empty ($this->content_row['description'])) {
1369            $html .= "<div class='user_ui_description'>\n";
1370            $description = self :: getObject($this->content_row['description']);
1371            $description->setLogAsContent($this);
1372            // If the content logging is disabled, all the children will inherit this property temporarly
1373            if ($this->getLoggingStatus() == false)
1374            $description->setLoggingStatus(false);
1375            $html .= $description->getUserUI();
1376            $html .= "</div>\n";
1377        }
1378
1379        if (!empty ($this->content_row['project_info'])) {
1380            if (!empty ($this->content_row['project_info'])) {
1381                $html .= "<div class='user_ui_projet_info'>\n";
1382                $html .= "<b>" . _("Project information:") . "</b>";
1383                $project_info = self :: getObject($this->content_row['project_info']);
1384                $project_info->setLogAsContent($this);
1385                // If the content logging is disabled, all the children will inherit this property temporarly
1386                if ($this->getLoggingStatus() == false)
1387                $project_info->setLoggingStatus(false);
1388                $html .= $project_info->getUserUI();
1389                $html .= "</div>\n";
1390            }
1391        }
1392        if ($hasDisplayableMetadata) {
1393            $html .= "</td>\n";
1394            $html .= "</tr>\n";
1395        }
1396        if($this->user_ui_interaction_area) {
1397            if (!$hasDisplayableMetadata) {
1398                $html .= "\n<div class='user_ui_interaction_area'>$this->user_ui_interaction_area</div>\n";
1399            } else {
1400                $html .= "<tr>\n";
1401                $html .= "<td colspan=2 class='user_ui_interaction_area'>\n$this->user_ui_interaction_area</td>\n";
1402                $html .= "</tr>\n";
1403            }
1404        }
1405
1406        if ($hasDisplayableMetadata) {
1407            $html .= "</table>\n";
1408        }
1409        $html .= "</div>\n";
1410        $html .= "</div>\n";
1411
1412
1413        $this->logContentDisplay();
1414        return $html;
1415    }
1416
1417    /** Allow logging as part of another content (usually the parent for metadata).
1418     * Redirects clickthrough logging to the parent's content id, and does not log
1419     * display */
1420    protected function setLogAsContent(Content $content) {
1421        $this->log_as_content = $content;
1422    }
1423
1424    /** Get the last time this content was displayed
1425     * @param $user User, optionnal.  If present, the date is the last time the
1426     * content was displayed for this user
1427     * @param $node Node, optionnal.  If present, the date is the last time the
1428     * content was displayed at this node
1429     @return PHP timestamp or null */
1430    public function getLastDisplayedTimestamp($user = null, $node = null) {
1431        $retval = null;
1432        $log_row = null;
1433        $user ? $user_sql = " AND user_id='{$user->getId()}' " : $user_sql = '';
1434        $node ? $node_sql = " AND node_id='{$node->getId()}' " : $node_sql = '';
1435        $db = AbstractDb :: getObject();
1436
1437        $sql = "SELECT EXTRACT('epoch' FROM MAX(last_display_timestamp)) as last_display_timestamp FROM content_display_log WHERE content_id='$this->id' $user_sql $node_sql";
1438        $db->execSqlUniqueRes($sql, $log_row, false);
1439        if ($log_row != null) {
1440            $retval = $log_row['last_display_timestamp'];
1441        }
1442        return $retval;
1443    }
1444
1445    /** Log that this content has just been displayed to the user.  Will only log if the user is logged in */
1446    private function logContentDisplay() {
1447        if ($this->getLoggingStatus() == true && $this->log_as_content->getId() == $this->getId()) {
1448            // DEBUG::
1449            //echo "Logging ".get_class($this)." :: ".$this->__toString()."<br>";
1450            $user = User :: getCurrentUser();
1451            $node = Node :: getCurrentNode();
1452            if ($user != null && $node != null) {
1453                $user_id = $user->getId();
1454                $node_id = $node->getId();
1455                $db = AbstractDb :: getObject();
1456
1457                $sql = "SELECT * FROM content_display_log WHERE content_id='$this->id' AND user_id='$user_id' AND node_id='$node_id'";
1458                $db->execSql($sql, $log_rows, false);
1459                if ($log_rows != null) {
1460                    $sql = "UPDATE content_display_log SET num_display = num_display +1, last_display_timestamp = CURRENT_TIMESTAMP WHERE content_id='$this->id' AND user_id='$user_id' AND node_id='$node_id'";
1461                } else {
1462                    $sql = "INSERT INTO content_display_log (user_id, content_id, node_id) VALUES ('$user_id', '$this->id', '$node_id')";
1463                }
1464                $db->execSqlUpdate($sql, false);
1465            }
1466        }
1467    }
1468    /** Handle replacements of hyperlinks for clickthrough tracking (if appropriate) */
1469    protected function replaceHyperLinks(& $html) {
1470        /* Handle hyperlink clicktrough logging */
1471        if ($this->getLoggingStatus() == true) {
1472            $html = HyperLinkUtils :: replaceHyperLinks($html, $this->log_as_content);
1473        }
1474        return $html;
1475    }
1476
1477    /** Retreives the list interface of this object.  Anything that overrides this method should call the parent method with it's output at the END of processing.
1478     * @param $subclass_admin_interface Html content of the interface element of a children
1479     * @return The HTML fragment for this interface */
1480    public function getListUI($subclass_list_interface = null) {
1481        $html = '';
1482        $html .= "<div class='list_ui_container'>\n";
1483        $html .= $this->__toString()."\n";
1484        $html .= $subclass_list_interface;
1485        $html .= "</div>\n";
1486        return $html;
1487    }
1488
1489    /**
1490     * Retreives the admin interface of this object. Anything that overrides
1491     * this method should call the parent method with it's output at the END of
1492     * processing.
1493     * @param string $subclass_admin_interface HTML content of the interface
1494     * element of a children.
1495     * @return string The HTML fragment for this interface.
1496     */
1497    public function getAdminUI($subclass_admin_interface = null, $title = null) {
1498        $db = AbstractDb :: getObject();
1499
1500        $html = '';
1501        if (!(User :: getCurrentUser()->DEPRECATEDisSuperAdmin() || $this->DEPRECATEDisOwner(User :: getCurrentUser()))) {
1502            $html .= $this->getListUI();
1503            $html .= ' ' . _("(You do not have access to edit this piece of content)");
1504        } else {
1505            $html .= "<fieldset class='admin_container " . get_class($this) . "'>\n";
1506            if (!empty ($title)) {
1507                $html .= "<legend>$title</legend>\n";
1508            }
1509            $html .= "<ul class='admin_element_list'>\n";
1510            if ($this->getObjectType() == 'Content') {
1511                // The object hasn't yet been typed.
1512                $html .= _("You must select a content type: ");
1513                $i = 0;
1514
1515                foreach (self :: getAvailableContentTypes() as $classname) {
1516                    $tab[$i][0] = $classname;
1517                    $tab[$i][1] = $classname;
1518                    $i++;
1519                }
1520
1521                $html .= FormSelectGenerator :: generateFromArray($tab, null, "content_" . $this->id . "_content_type", "Content", false);
1522            } else {
1523                $criteria_array = array (
1524                array (
1525                'isSimpleContent'
1526                )
1527                );
1528                $metadada_allowed_content_types = ContentTypeFilter :: getObject($criteria_array);
1529
1530                // Content metadata
1531                if ($this->isSimpleContent() == false || $this->isPersistent()) {
1532                    $html .= "<fieldset class='admin_element_group'>\n";
1533                    $html .= "<legend>" . sprintf(_("%s MetaData"), get_class($this)) . "</legend>\n";
1534
1535                    /* title_is_displayed */
1536                    $html_title_is_displayed = _("Display the title?") . ": \n";
1537                    $name = "content_" . $this->id . "_title_is_displayed";
1538                    $this->titleShouldDisplay() ? $checked = 'CHECKED' : $checked = '';
1539                    $html_title_is_displayed .= "<input type='checkbox' name='$name' $checked>\n";
1540
1541                    /* title */
1542                    $html .= "<li class='admin_element_item_container admin_section_edit_title'>\n";
1543                    $html .= "<div class='admin_element_data'>\n";
1544                    if (empty ($this->content_row['title'])) {
1545                        $html .= self :: getNewContentUI("title_{$this->id}_new", $metadada_allowed_content_types, _("Title:"));
1546                        $html .= "</div>\n";
1547                    } else {
1548                        $html .= $html_title_is_displayed;
1549                        $title = self :: getObject($this->content_row['title']);
1550                        $html .= $title->getAdminUI(null, _("Title:"));
1551                        $html .= "</div>\n";
1552                        $html .= "<div class='admin_element_tools admin_section_delete_title'>\n";
1553                        $name = "content_" . $this->id . "_title_erase";
1554                        $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s (%s)"), _("title"), get_class($title)) . "'>";
1555                        $html .= "</div>\n";
1556                    }
1557                    $html .= "</li>\n";
1558                }
1559
1560                if ($this->isSimpleContent() == false) {
1561                    /* description */
1562                    $html .= "<li class='admin_element_item_container admin_section_edit_description'>\n";
1563                    $html .= "<div class='admin_element_data'>\n";
1564                    if (empty ($this->content_row['description'])) {
1565                        $html .= self :: getNewContentUI("description_{$this->id}_new", $metadada_allowed_content_types, _("Description:"));
1566                        $html .= "</div>\n";
1567                    } else {
1568                        $description = self :: getObject($this->content_row['description']);
1569                        $html .= $description->getAdminUI(null, _("Description:"));
1570                        $html .= "</div>\n";
1571                        $html .= "<div class='admin_element_tools'>\n";
1572                        $name = "content_" . $this->id . "_description_erase";
1573                        $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s (%s)"), _("description"), get_class($description)) . "'>";
1574                        $html .= "</div>\n";
1575                    }
1576                    $html .= "</li>\n";
1577
1578                    /* long description */
1579                    $html .= "<li class='admin_element_item_container admin_section_edit_long_description'>\n";
1580                    $html .= "<div class='admin_element_data'>\n";
1581                    if (empty ($this->content_row['long_description'])) {
1582                        $html .= self :: getNewContentUI("long_description_{$this->id}_new", $metadada_allowed_content_types, _("Long description:"));
1583                        $html .= "</div>\n";
1584                    } else {
1585                        $description = self :: getObject($this->content_row['long_description']);
1586                        $html .= $description->getAdminUI(null, _("Long description:"));
1587                        $html .= "</div>\n";
1588                        $html .= "<div class='admin_element_tools'>\n";
1589                        $name = "content_" . $this->id . "_long_description_erase";
1590                        $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s (%s)"), _("long description"), get_class($description)) . "'>";
1591                        $html .= "</div>\n";
1592                    }
1593                    $html .= "</li>\n";
1594
1595                    /* project_info */
1596                    $html .= "<li class='admin_element_item_container admin_section_edit_project'>\n";
1597                    $html .= "<div class='admin_element_data'>\n";
1598                    if (empty ($this->content_row['project_info'])) {
1599                        $html .= self :: getNewContentUI("project_info_{$this->id}_new", $metadada_allowed_content_types, _("Information on this project:"));
1600                        $html .= "</div>\n";
1601                    } else {
1602                        $project_info = self :: getObject($this->content_row['project_info']);
1603                        $html .= $project_info->getAdminUI(null, _("Information on this project:"));
1604                        $html .= "</div>\n";
1605                        $html .= "<div class='admin_element_tools'>\n";
1606                        $name = "content_" . $this->id . "_project_info_erase";
1607                        $html .= "<input type='submit' class='submit' name='$name' value='" . sprintf(_("Delete %s (%s)"), _("project information"), get_class($project_info)) . "'>";
1608                        $html .= "</div>\n";
1609                    }
1610                    $html .= "</li>\n";
1611                }
1612
1613                //End content medatada
1614                if ($this->isSimpleContent() == false || $this->isPersistent()) {
1615                    $html .= "</fieldset>\n";
1616                }
1617
1618                if ($this->isSimpleContent() == false || $this->isPersistent()) {
1619
1620                    $html .= "<fieldset class='admin_element_group'>\n";
1621                    $html .= "<legend>" . sprintf(_("%s access control"), get_class($this)) . "</legend>\n";
1622
1623                    /* is_persistent */
1624                    $html .= "<li class='admin_element_item_container admin_section_edit_persistant'>\n";
1625                    $html .= "<div class='admin_element_label'>" . _("Is part of reusable content library (protected from deletion)?") . ": </div>\n";
1626                    $html .= "<div class='admin_element_data'>\n";
1627                    $name = "content_" . $this->id . "_is_persistent";
1628                    $this->isPersistent() ? $checked = 'CHECKED' : $checked = '';
1629                    $html .= "<input type='checkbox' name='$name' $checked onChange='submit();'>\n";
1630                    $html .= "</div>\n";
1631                    $html .= "</li>\n";
1632
1633                    /* content_has_owners */
1634                    $html .= "<li class='admin_element_item_container content_has_owners'>\n";
1635                    $html .= "<div class='admin_element_label'>" . _("Content owner list") . "</div>\n";
1636                    $html .= "<ul class='admin_element_list'>\n";
1637
1638                    $db = AbstractDb :: getObject();
1639                    $sql = "SELECT * FROM content_has_owners WHERE content_id='$this->id'";
1640                    $db->execSql($sql, $content_owner_rows, false);
1641                    if ($content_owner_rows != null) {
1642                        foreach ($content_owner_rows as $content_owner_row) {
1643                            $html .= "<li class='admin_element_item_container'>\n";
1644                            $html .= "<div class='admin_element_data'>\n";
1645                            $user = User :: getObject($content_owner_row['user_id']);
1646
1647                            $html .= $user->getListUI();
1648                            $name = "content_" . $this->id . "_owner_" . $user->GetId() . "_is_author";
1649                            $html .= " Is content author? ";
1650
1651                            $content_owner_row['is_author'] == 't' ? $checked = 'CHECKED' : $checked = '';
1652                            $html .= "<input type='checkbox' name='$name' $checked>\n";
1653                            $html .= "</div>\n";
1654                            $html .= "<div class='admin_element_tools'>\n";
1655                            $name = "content_" . $this->id . "_owner_" . $user->GetId() . "_remove";
1656                            $html .= "<input type='submit' class='submit' name='$name' value='" . _("Remove owner") . "'>";
1657                            $html .= "</div>\n";
1658                            $html .= "</li>\n";
1659                        }
1660                    }
1661
1662                    $html .= "<li class='admin_element_item_container'>\n";
1663                    $html .= "<div class='admin_element_data'>\n";
1664                    $add_button_name = "content_{$this->id}_add_owner_submit";
1665                    $add_button_value = _("Add owner");
1666                    $html .= User :: getSelectUserUI("content_{$this->id}_new_owner", $add_button_name, $add_button_value);
1667                    $html .= "</div>\n";
1668                    $html .= "</li>\n";
1669                    $html .= "</ul>\n";
1670                    $html .= "</li>\n";
1671                    $html .= "</fieldset>\n";
1672
1673                }
1674            }
1675            $html .= $subclass_admin_interface;
1676            $html .= "</ul>\n";
1677            $html .= "</fieldset>\n";
1678        }
1679        return $html;
1680    }
1681    /** Process admin interface of this object.  When an object overrides this method, they should call the parent processAdminUI at the BEGINING of processing.
1682
1683    */
1684    public function processAdminUI() {
1685        if ($this->DEPRECATEDisOwner(User :: getCurrentUser()) || User :: getCurrentUser()->DEPRECATEDisSuperAdmin()) {
1686            $db = AbstractDb :: getObject();
1687            if ($this->getObjectType() == 'Content') /* The object hasn't yet been typed */ {
1688                $content_type = FormSelectGenerator :: getResult("content_" . $this->id . "_content_type", "Content");
1689                $this->setContentType($content_type);
1690            } else {
1691                //Content medatada
1692
1693                if ($this->isSimpleContent() == false || $this->isPersistent()) {
1694                    /* title_is_displayed */
1695                    if (!empty ($this->content_row['title'])) {
1696                        $name = "content_" . $this->id . "_title_is_displayed";
1697                        !empty ($_REQUEST[$name]) ? $this->setTitleIsDisplayed(true) : $this->setTitleIsDisplayed(false);
1698                    }
1699                    /* title */
1700                    if (empty ($this->content_row['title'])) {
1701                        $title = self :: processNewContentUI("title_{$this->id}_new");
1702                        if ($title != null) {
1703                            $title_id = $title->GetId();
1704                            $db->execSqlUpdate("UPDATE content SET title = '$title_id' WHERE content_id = '$this->id'", FALSE);
1705                        }
1706                    } else {
1707                        $title = self :: getObject($this->content_row['title']);
1708                        $name = "content_" . $this->id . "_title_erase";
1709                        if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
1710                            $db->execSqlUpdate("UPDATE content SET title = NULL WHERE content_id = '$this->id'", FALSE);
1711                            $title->delete($errmsg);
1712                        } else {
1713                            $title->processAdminUI();
1714                        }
1715                    }
1716                }
1717                if ($this->isSimpleContent() == false) {
1718                    /* description */
1719                    if (empty ($this->content_row['description'])) {
1720                        $description = self :: processNewContentUI("description_{$this->id}_new");
1721                        if ($description != null) {
1722                            $description_id = $description->GetId();
1723                            $db->execSqlUpdate("UPDATE content SET description = '$description_id' WHERE content_id = '$this->id'", FALSE);
1724                        }
1725                    } else {
1726                        $description = self :: getObject($this->content_row['description']);
1727                        $name = "content_" . $this->id . "_description_erase";
1728                        if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
1729                            $db->execSqlUpdate("UPDATE content SET description = NULL WHERE content_id = '$this->id'", FALSE);
1730                            $description->delete($errmsg);
1731                        } else {
1732                            $description->processAdminUI();
1733                        }
1734                    }
1735
1736                    /* long description */
1737                    if (empty ($this->content_row['long_description'])) {
1738                        $long_description = self :: processNewContentUI("long_description_{$this->id}_new");
1739                        if ($long_description != null) {
1740                            $long_description_id = $long_description->GetId();
1741                            $db->execSqlUpdate("UPDATE content SET long_description = '$long_description_id' WHERE content_id = '$this->id'", FALSE);
1742                        }
1743                    } else {
1744                        $long_description = self :: getObject($this->content_row['long_description']);
1745                        $name = "content_" . $this->id . "_long_description_erase";
1746                        if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
1747                            $db->execSqlUpdate("UPDATE content SET long_description = NULL WHERE content_id = '$this->id'", FALSE);
1748                            $long_description->delete($errmsg);
1749                        } else {
1750                            $long_description->processAdminUI();
1751                        }
1752                    }
1753
1754                    /* project_info */
1755                    if (empty ($this->content_row['project_info'])) {
1756                        $project_info = self :: processNewContentUI("project_info_{$this->id}_new");
1757                        if ($project_info != null) {
1758                            $project_info_id = $project_info->GetId();
1759                            $db->execSqlUpdate("UPDATE content SET project_info = '$project_info_id' WHERE content_id = '$this->id'", FALSE);
1760                        }
1761                    } else {
1762                        $project_info = self :: getObject($this->content_row['project_info']);
1763                        $name = "content_" . $this->id . "_project_info_erase";
1764                        if (!empty ($_REQUEST[$name]) && $_REQUEST[$name] == true) {
1765                            $db->execSqlUpdate("UPDATE content SET project_info = NULL WHERE content_id = '$this->id'", FALSE);
1766                            $project_info->delete($errmsg);
1767                        } else {
1768                            $project_info->processAdminUI();
1769                        }
1770                    }
1771                } //End content metadata
1772
1773                if ($this->isSimpleContent() == false || $this->isPersistent()) {
1774                    /* is_persistent */
1775                    $name = "content_" . $this->id . "_is_persistent";
1776                    !empty ($_REQUEST[$name]) ? $this->setIsPersistent(true) : $this->setIsPersistent(false);
1777
1778                    /* content_has_owners */
1779                    $sql = "SELECT * FROM content_has_owners WHERE content_id='$this->id'";
1780                    $db->execSql($sql, $content_owner_rows, false);
1781                    if ($content_owner_rows != null) {
1782                        foreach ($content_owner_rows as $content_owner_row) {
1783                            $user = User :: getObject($content_owner_row['user_id']);
1784                            $user_id = $user->getId();
1785                            $name = "content_" . $this->id . "_owner_" . $user->GetId() . "_remove";
1786                            if (!empty ($_REQUEST[$name])) {
1787                                $this->deleteOwner($user);
1788                            } else {
1789                                $name = "content_" . $this->id . "_owner_" . $user->GetId() . "_is_author";
1790                                $content_owner_row['is_author'] == 't' ? $is_author = true : $is_author = false;
1791                                !empty ($_REQUEST[$name]) ? $should_be_author = true : $should_be_author = false;
1792                                if ($is_author != $should_be_author) {
1793                                    $should_be_author ? $is_author_sql = 'TRUE' : $is_author_sql = 'FALSE';
1794                                    $sql = "UPDATE content_has_owners SET is_author=$is_author_sql WHERE content_id='$this->id' AND user_id='$user_id'";
1795
1796                                    if (!$db->execSqlUpdate($sql, false)) {
1797                                        throw new Exception(_('Unable to set as author in the database.'));
1798                                    }
1799
1800                                }
1801
1802                            }
1803                        }
1804                    }
1805                    $errMsg=null;
1806                    $user = User :: processSelectUserUI("content_{$this->id}_new_owner", $errMsg);
1807                    $name = "content_{$this->id}_add_owner_submit";
1808                    if (!empty ($_REQUEST[$name]) && $user != null) {
1809                        $this->addOwner($user);
1810                    }
1811                }
1812            }
1813            $this->refresh();
1814        }
1815    }
1816
1817    /**
1818     * Tell if a given user is already subscribed to this content
1819     * @param User the given user
1820     * @return boolean
1821     */
1822    public function isUserSubscribed(User $user) {
1823        $db = AbstractDb :: getObject();
1824        $sql = "SELECT content_id FROM user_has_content WHERE user_id = '{$user->getId()}' AND content_id = '{$this->getId()}';";
1825        $db->execSqlUniqueRes($sql, $row, false);
1826
1827        if ($row)
1828        return true;
1829        else
1830        return false;
1831    }
1832
1833    /** Subscribe to the project
1834     * @return true on success, false on failure */
1835    public function subscribe(User $user) {
1836        return $user->addContent($this);
1837    }
1838    /** Unsubscribe to the project
1839     * @return true on success, false on failure */
1840    public function unsubscribe(User $user) {
1841        return $user->removeContent($this);
1842    }
1843
1844    /** If the title is not empty, should it be displayed?
1845     * @return true or false */
1846    public function titleShouldDisplay() {
1847        if ($this->content_row['title_is_displayed'] == 't') {
1848            $retval = true;
1849        } else {
1850            $retval = false;
1851        }
1852        return $retval;
1853    }
1854
1855    /** If the title is not empty, should it be displayed?
1856     * @param $should_display true or false
1857     * */
1858    public function setTitleIsDisplayed($should_display) {
1859        if ($should_display != $this->titleShouldDisplay()) /* Only update database if there is an actual change */ {
1860            $should_display ? $should_display_sql = 'TRUE' : $should_display_sql = 'FALSE';
1861            $db = AbstractDb :: getObject();
1862            $db->execSqlUpdate("UPDATE content SET title_is_displayed = $should_display_sql WHERE content_id = '$this->id'", false);
1863            $this->refresh();
1864        }
1865
1866    }
1867
1868    /** Persistent (or read-only) content is meant for re-use.  It will not be deleted when the delete() method is called.  When a containing element (ContentGroup, ContentGroupElement) is deleted, it calls delete on all the content it includes.  If the content is persistent, only the association will be removed.
1869     * @return true or false */
1870    public function isPersistent() {
1871        if ($this->content_row['is_persistent'] == 't') {
1872            $retval = true;
1873        } else {
1874            $retval = false;
1875        }
1876        return $retval;
1877    }
1878
1879    /** Set if the content group is persistent
1880     * @param $is_persistent true or false
1881     * */
1882    public function setIsPersistent($is_persistent) {
1883        if ($is_persistent != $this->isPersistent()) /* Only update database if there is an actual change */ {
1884            $is_persistent ? $is_persistent_sql = 'TRUE' : $is_persistent_sql = 'FALSE';
1885
1886            $db = AbstractDb :: getObject();
1887            $db->execSqlUpdate("UPDATE content SET is_persistent = $is_persistent_sql WHERE content_id = '$this->id'", false);
1888            $this->refresh();
1889        }
1890
1891    }
1892
1893    /**
1894     * Return update date
1895     *
1896     * @return string ISO-8601-2000 timestamp
1897     */
1898    public function getLastUpdateTimestamp() {
1899        return $this->content_row['last_update_timestamp'];
1900    }
1901    /**
1902     * Touch countent:  set last_update_timestamp to now
1903     * Note that this is meant to be called when there is substantial
1904     *  change to the actual content (the file changed, the main text changed,
1905     * etc.)
1906     */
1907    public function touch() {
1908        $this->mBd->execSqlUpdate("UPDATE content SET last_update_timestamp = CURRENT_TIMESTAMP WHERE content_id='" . $this->getId() . "'", false);
1909        $this->refresh();
1910    }
1911    /** Reloads the object from the database.  Should normally be called after a set operation.
1912     * This function is private because calling it from a subclass will call the
1913     * constructor from the wrong scope */
1914    private function refresh() {
1915        $this->__construct($this->id);
1916    }
1917
1918    /**
1919     * @see GenericObject
1920     * @internal Persistent content will not be deleted
1921     */
1922    public function delete(& $errmsg) {
1923        $retval = false;
1924        if ($this->isPersistent()) {
1925            $errmsg = _("Content is persistent (you must make it non persistent before you can delete it)");
1926        } else {
1927            $db = AbstractDb :: getObject();
1928            if ($this->DEPRECATEDisOwner(User :: getCurrentUser()) || User :: getCurrentUser()->DEPRECATEDisSuperAdmin()) {
1929
1930                $sql = "DELETE FROM content WHERE content_id='$this->id'";
1931                $db->execSqlUpdate($sql, false);
1932                //Metadata mmust be deleted AFTER the main content.
1933                $errmsgTmp = null;
1934                $metadata = $this->getTitle();
1935                if ($metadata){
1936                    $metadata->delete($errmsgTmp);
1937                }
1938                $errmsg .= $errmsgTmp;
1939                $errmsgTmp = null;
1940                $metadata = $this->getDescription();
1941                if ($metadata){
1942                    $metadata->delete($errmsgTmp);
1943                }
1944                $errmsg .= $errmsgTmp;
1945                $errmsgTmp = null;
1946                $metadata = $this->getLongDescription();
1947                if ($metadata){
1948                    $metadata->delete($errmsgTmp);
1949                }
1950                $errmsg .= $errmsgTmp;
1951                $errmsgTmp = null;
1952                $metadata = $this->getProjectInfo();
1953                if ($metadata){
1954                    $metadata->delete($errmsgTmp);
1955                }
1956                $errmsg .= $errmsgTmp;
1957                $retval = true;
1958            } else {
1959                $errmsg = _("Access denied (not owner of content)");
1960            }
1961        }
1962        return $retval;
1963    }
1964    /** Menu hook function */
1965    static public function hookMenu() {
1966        $items = array();
1967        $server = Server::getServer();
1968        if(Security::hasAnyPermission(array(array(Permission::P('SERVER_PERM_EDIT_CONTENT_LIBRARY'), $server))))
1969        {
1970            $items[] = array('path' => 'server/content_library',
1971            'title' => _("Reusable content library"),
1972            'url' => BASE_URL_PATH.htmlspecialchars("admin/generic_object_admin.php?object_class=Content&action=list")
1973            );
1974        }
1975
1976        return $items;
1977    }
1978
1979
1980} // End class
1981
1982/* This allows the class to enumerate it's children properly */
1983$class_names = Content :: getAvailableContentTypes();
1984
1985foreach ($class_names as $class_name) {
1986    /**
1987     * Load requested content class
1988     */
1989    require_once ('classes/Content/' . $class_name . '/' . $class_name . '.php');
1990}
1991
1992/*
1993 * Local variables:
1994 * tab-width: 4
1995 * c-basic-offset: 4
1996 * c-hanging-comment-ender-p: nil
1997 * End:
1998 */
Note: See TracBrowser for help on using the browser.