root/trunk/wifidog-auth/wifidog/classes/Content/Langstring/Langstring.php @ 1235

Revision 1235, 26.8 KB (checked in by benoitg, 6 years ago)
  • Reverse part of [1230], hotspot_network_name was already available as standardised variable networkName, and hotspot_network_url as networkWebSiteURL.
  • Reverse part of [1231]: remove script tag, banning it was the reason the sag striping code was put in in the first place. We may want to consider creating a RawHTMLContent Content Type.
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2
3
4/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5
6// +-------------------------------------------------------------------+
7// | WiFiDog Authentication Server                                     |
8// | =============================                                     |
9// |                                                                   |
10// | The WiFiDog Authentication Server is part of the WiFiDog captive  |
11// | portal suite.                                                     |
12// +-------------------------------------------------------------------+
13// | PHP version 5 required.                                           |
14// +-------------------------------------------------------------------+
15// | Homepage:     http://www.wifidog.org/                             |
16// | Source Forge: http://sourceforge.net/projects/wifidog/            |
17// +-------------------------------------------------------------------+
18// | This program is free software; you can redistribute it and/or     |
19// | modify it under the terms of the GNU General Public License as    |
20// | published by the Free Software Foundation; either version 2 of    |
21// | the License, or (at your option) any later version.               |
22// |                                                                   |
23// | This program is distributed in the hope that it will be useful,   |
24// | but WITHOUT ANY WARRANTY; without even the implied warranty of    |
25// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     |
26// | GNU General Public License for more details.                      |
27// |                                                                   |
28// | You should have received a copy of the GNU General Public License |
29// | along with this program; if not, contact:                         |
30// |                                                                   |
31// | Free Software Foundation           Voice:  +1-617-542-5942        |
32// | 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        |
33// | Boston, MA  02111-1307,  USA       gnu@gnu.org                    |
34// |                                                                   |
35// +-------------------------------------------------------------------+
36
37/**
38 * @package    WiFiDogAuthServer
39 * @subpackage ContentClasses
40 * @author     Benoit Grégoire <bock@step.polymtl.ca>
41 * @copyright  2004-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/Cache.php');
50require_once ('classes/HtmlSafe.php');
51require_once ('classes/LocaleList.php');
52
53/**
54 * Représente un Langstring en particulier, ne créez pas un objet langstrings
55 * si vous n'en avez pas spécifiquement besoin
56 *
57 * @package    WiFiDogAuthServer
58 * @subpackage ContentClasses
59 * @author     Benoit Grégoire <bock@step.polymtl.ca>
60 * @copyright  2004-2006 Benoit Grégoire, Technologies Coeus inc.
61 */
62class Langstring extends Content {
63    protected $allowed_html_tags;
64    /**
65     * Constructor
66     *
67     * @param string $content_id Content id
68     */
69    protected function __construct($content_id)
70    {
71        parent::__construct($content_id);
72        $db = AbstractDb::getObject();
73            /**
74         * HTML allowed to be used
75        */
76                $this->allowed_html_tags = "<a><br><b><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><strong><u><ul><li><br/><hr><div></div>";
77        $this->mBd = &$db;
78    }
79
80    /** Indicate that the content is suitable to store plain text.
81     * @return true or false */
82    public function isTextualContent() {
83        return true;
84    }
85
86    /**
87     * Returns the first available string in the user's language, faling that in the
88     * same major language (first part of the locale), failing that the first available
89     * string
90     * @param bool verbose : Should the function verbose when a string is empty ?
91     * @return UTF-8 string
92     */
93    public function getString($verbose = true) {
94        // Init values
95        $retval = null;
96        $row = null;
97        $_useCache = false;
98        $_cachedData = null;
99
100        // Create new cache objects
101        $_cacheLanguage = new Cache('langstrings_' . $this->id . '_substring_' . substr(Locale :: getCurrentLocale()->getId(), 0, 2) . '_string', $this->id);
102        $_cache = new Cache('langstrings_' . $this->id . '_substring__string', $this->id);
103
104        // Check if caching has been enabled.
105        if ($_cacheLanguage->isCachingEnabled) {
106            $_cachedData = $_cacheLanguage->getCachedData();
107            if ($_cachedData) {
108                // Return cached data.
109                $_useCache = true;
110                $retval = $_cachedData;
111            } else {
112                // Language specific cached data has not been found.
113                // Try to get language independent cached data.
114                if ($_cachedData = $_cache->getCachedData()) {
115                    // Return cached data.
116                    $_useCache = true;
117                    $retval = $_cachedData;
118                }
119            }
120        }
121
122        if (!$_useCache) {
123            //Get user's prefered language
124            $sql = "SELECT value, locales_id, \n";
125            $sql .= Locale :: getSqlCaseStringSelect(Locale :: getCurrentLocale()->getId());
126            $sql .= " as score FROM content_langstring_entries WHERE content_langstring_entries.langstrings_id = '{$this->id}' AND value!='' ORDER BY score LIMIT 1";
127            $this->mBd->execSqlUniqueRes($sql, $row, false);
128
129            if ($row == null) {
130                if($verbose == true)
131                $retval = sprintf(_("(Empty %s)"), get_class($this));
132                else
133                $retval = "";
134            } else {
135                $retval = $row['value'];
136
137                // Check if caching has been enabled.
138                if ($_cache->isCachingEnabled) {
139                    // Save data into cache, because it wasn't saved into cache before.
140                    $_cache->saveCachedData($retval);
141                }
142            }
143        }
144
145        return $retval;
146    }
147
148    /**
149     * Ajoute une chaîne de caractère au Langstring
150     *
151     * @param string $string             La chaîne de caractère à ajouter.  Si la chaîne
152     *                                   est vide ('') ou null, la fonction retourne sans
153     *                                   toucher à la base de donnée
154     * @param string $locale             La langue régionale de la chaîne ajoutée, exemple:
155     *                                   'fr_CA', peut être NULL
156     * @param bool   $allow_empty_string Allow to store an empty string
157     *
158     * @return bool True si une chaîne a été ajoutée à la base de donnée,
159     * false autrement.
160     */
161    public function addString($string, $locale=null, $allow_empty_string = false) {
162        // Init values
163        $retval = false;
164        $id = 'NULL';
165        $idSQL = $id;
166
167        if ($locale) {
168            $language = new Locale($locale);
169            $id = $language->GetId();
170            $idSQL = "'" . $id . "'";
171        }
172
173        if ($allow_empty_string || ($string != null && $string != '')) {
174            $string = $this->mBd->escapeString($string);
175            $this->mBd->execSqlUpdate("INSERT INTO content_langstring_entries (langstring_entries_id, langstrings_id, locales_id, value) VALUES ('" . get_guid() . "', '$this->id', $idSQL , '$string')", FALSE);
176
177            // Create new cache object.
178            $_cache = new Cache('langstrings_' . $this->id . '_substring_' . $id . '_string', $this->id);
179
180            // Check if caching has been enabled.
181            if ($_cache->isCachingEnabled) {
182                // Remove old cached data.
183                $_cache->eraseCachedData();
184
185                // Save data into cache.
186                $_cache->saveCachedData($string);
187            }
188
189            $retval = true;
190        }
191
192        return $retval;
193    }
194
195    /**
196     * This method contains the interface to add an additional element to a
197     * content object.  (For example, a new string in a Langstring)
198     * It is called when getNewContentUI has only a single possible object type.
199     * It may also be called by the object getAdminUI to avoid code duplication.
200     *
201     * @param string $contentId      The id of the (possibly not yet created) content object.
202     *
203     * @param string $userData=null Array of contextual data optionally sent by displayAdminUI(),
204     *  and only understood by the class (or subclasses) where getNewUI() is defined.
205     *  The function must still function if none of it is present.
206     *
207     * @return HTML markup or false.  False means that this object does not support this interface.
208     */
209    public static function getNewUI($contentId, $userData=null) {
210        $html = '';
211        !empty($userData['excludeArray'])?$excludeArray=$userData['excludeArray']:$excludeArray=null;
212        !empty($userData['typeInterface'])?$typeInterface=$userData['typeInterface']:$typeInterface='LARGE';
213        $html .= "<div class='admin_element_data'>\n";
214        $liste_languages = new LocaleList();
215        $locale = LocaleList :: GetDefault();
216        $html .= _("Language") . ": " . $liste_languages->GenererFormSelect($locale, "langstrings_" . $contentId . "_substring_new_language", null, TRUE, $excludeArray);
217        $new_substring_name = "langstrings_" . $contentId . "_substring_new_string";
218
219        if ($typeInterface == 'LARGE') {
220            $html .= "<textarea name='$new_substring_name' class='textarea' cols='60' rows='3'></textarea>\n";
221        } else {
222            $html .= "<input type='text' name='$new_substring_name' class='input_text' size='80' value=''>\n";
223        }
224
225        $html .= "</div>\n";
226
227        $html .= "<div class='admin_element_tools'>\n";
228        $new_substring_submit_name = "langstrings_" . $contentId . "_add_new_entry";
229        $html .= "<input type='submit' class='submit' name='$new_substring_submit_name' value='" . _("Add new string") . "'>";
230        $html .= "</div>\n";
231        return $html;
232    }
233
234    /**
235     *
236     *
237     * @param string $contentId  The id of the (possibly not yet created) content object.
238     *
239     * @param string $checkOnly  If true, only check if there is data to be processed.
240     *  Will be used to decide if an object is to be created.  If there is
241     * processNewUI will typically be called again with $chechOnly=false
242     *
243     * @return true if there was data to be processed, false otherwise
244
245     */
246    public static function processNewUI($contentId, $checkOnly=false) {
247        $retval = false;
248        //Ajouter nouvelles chaîne(s) si champ non vide ou si l'usager a appuyé sur le bouton ajouter
249        $new_substring_name = "langstrings_" . $contentId . "_substring_new_string";
250        $new_substring_submit_name = "langstrings_" . $contentId . "_add_new_entry";
251
252        if ((isset ($_REQUEST[$new_substring_submit_name]) && $_REQUEST[$new_substring_submit_name] == true) || !empty ($_REQUEST[$new_substring_name])) {
253            $retval = true;
254            $generateur_form_select = new FormSelectGenerator();
255            $language = $generateur_form_select->getResult("langstrings_" . $contentId . "_substring_new_language", null);
256            if (empty ($language)) {
257                $language = null;
258            }
259            if(!$checkOnly) {
260                $langstring = self::getObject($contentId);
261                $langstring->addString($_REQUEST[$new_substring_name], $language, true);
262            }
263        }
264        return $retval;
265    }
266
267    /**
268     * Retreives the admin interface of this object. Anything that overrides
269     * this method should call the parent method with it's output at the END of
270     * processing.
271     * @param string $subclass_admin_interface HTML content of the interface
272     * element of a children.
273     * @param string $type_interface SIMPLE pour éditer un seul champ, COMPLETE
274     *                               pour voir toutes les chaînes, LARGE pour
275     *                               avoir un textarea.
276     * @return string The HTML fragment for this interface.
277     */
278    public function getAdminUI($subclass_admin_interface = null, $title = null, $type_interface = "LARGE") {
279        // Init values.
280        $html = '';
281        $html .= $subclass_admin_interface;
282        $result = "";
283        //$variantsCounter = 0;
284        //$_hideNewContent = false;
285        if (!empty ($this->allowed_html_tags)) {
286            $html .= "<div class='admin_section_hint'>" . _("Only these HTML tags are allowed : ") . htmlentities($this->allowed_html_tags) . "</div>";
287        }
288        $html .= "<ul class='admin_element_list'>\n";
289        $liste_languages = new LocaleList();
290        $sql = "SELECT * FROM content_langstring_entries WHERE content_langstring_entries.langstrings_id = '$this->id' ORDER BY locales_id";
291        $this->mBd->execSql($sql, $result, false); //echo "type_interface: $type_interface\n";
292
293        $exclude_array = array ();
294        if ($result != null) {
295            while (list ($key, $value) = each($result)) {
296                $exclude_array[$value['locales_id']] = $value['locales_id'];
297                //                The next lines are a preview of a new suggested input mode
298                //                ==========================================================
299                //
300                //                // Increase variants counter
301                //                $variantsCounter++;
302                //
303                //                // Hide new content input
304                //                $_hideNewContent = true;
305                //
306                //                $html .= "<li class='admin_element_item_container'>\n";
307                //                $html .= "<div class='admin_element_data'>\n";
308                //
309                //                if ($type_interface == 'LARGE') {
310                //                    $html .= "<textarea name='langstrings_".$this->id."_substring_$value[langstring_entries_id]_string' cols='60' rows='3'>".htmlspecialchars($value['value'], ENT_QUOTES, 'UTF-8')."</textarea>\n";
311                //                } else {
312                //                    $html .= "<input type='text' name='langstrings_".$this->id."_substring_$value[langstring_entries_id]_string' size='44' value='".htmlspecialchars($value['value'], ENT_QUOTES, 'UTF-8')."'>\n";
313                //                }
314                //
315                //                $html .= "<div class='admin_element_data' id='langstrings_".$this->id."_substring_$value[langstring_entries_id]_language_section' style='display: none;'>\n";
316                //                $html .= $liste_languages->GenererFormSelect("$value[locales_id]", "langstrings_".$this->id."_substring_$value[langstring_entries_id]_language", null, TRUE);
317                //                $html .= "</div>\n";
318                //
319                //                $html .= "</div>\n";
320                //                $html .= "<div class='admin_element_tools'>\n";
321                //                $name = "langstrings_".$this->id."_substring_$value[langstring_entries_id]_erase";
322                //
323                //                // Choose language button
324                //                $html .= "<a href='javascript:showHideView(\"langstrings_".$this->id."_substring_$value[langstring_entries_id]_language_section\", \"langstrings_".$this->id."_substring_$value[langstring_entries_id]_language_section_image\");'><img src='" . BASE_SSL_PATH . "images/icons/language.gif' id='langstrings_".$this->id."_substring_$value[langstring_entries_id]_language_section_image' class='admin_section_button' alt='"._("Choose language")."' title='"._("Choose language")."'></a>";
325                //
326                //                // Add string button
327                //                if (count($result) == $variantsCounter) {
328                //                    // This is the last string variant - show "add string" button.
329                //                    $html .= "<a href='javascript:showHideView(\"langstrings_".$this->id."_add_new_entry_view\", \"langstrings_".$this->id."_add_new_entry_image\");'><img src='" . BASE_SSL_PATH . "images/icons/add.gif' id='langstrings_".$this->id."_add_new_entry_image' class='admin_section_button' alt='"._("Add new string")."' title='"._("Add new string")."'></a>";
330                //                } else {
331                //                    $html .= "<img src='" . BASE_SSL_PATH . "images/icons/add.gif' id='langstrings_".$this->id."_add_new_entry_image' class='admin_section_button_disabled' alt='"._("Add new string")."' title='"._("Add new string")."'>";
332                //                }
333                //
334                //                // Delete string button
335                //                $html .= "<input type='image' name='$name' class='admin_section_button' src='" . BASE_SSL_PATH . "images/icons/delete.gif' alt='"._("Delete string")."' title='"._("Delete string")."'>";
336                //
337                //                $html .= "</div>\n";
338                //                $html .= "</li>\n";
339
340                $html .= "<li class='admin_element_item_container'>\n";
341                $html .= "<div class='admin_element_data'>\n";
342
343                $html .= _("Language") . ": " . $liste_languages->GenererFormSelect("$value[locales_id]", "langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_language", null, TRUE);
344
345                if ($type_interface == 'LARGE') {
346                    $html .= "<textarea name='langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_string' class='textarea' cols='60' rows='3'>" . htmlspecialchars($value['value'], ENT_QUOTES, 'UTF-8') . "</textarea>\n";
347                } else {
348                    $html .= "<input type='text' size='80' class='input_text' name='langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_string' value='" . htmlspecialchars($value['value'], ENT_QUOTES, 'UTF-8') . "'>\n";
349                }
350
351                $html .= "</div>\n";
352                $html .= "<div class='admin_element_tools'>\n";
353                $name = "langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_erase";
354                $html .= "<input type='submit' class='submit' name='$name' value='" . _("Delete string") . "'>";
355                $html .= "</div>\n";
356                $html .= "</li>\n";
357            }
358        }
359
360        //        The next lines are a preview of a new suggested input mode
361        //        ==========================================================
362        //
363        //        //Nouvelles chaîne
364        //        $locale = LocaleList :: GetDefault();
365        //
366        //        $html .= "<li class='admin_element_item_container' id='langstrings_".$this->id."_add_new_entry_view'" . ($_hideNewContent ? " style='display: none;'" : "") . ">\n";
367        //        $html .= "<div class='admin_element_data'>\n";
368        //
369        //        $new_substring_name = "langstrings_".$this->id."_substring_new_string";
370        //
371        //        if ($type_interface == 'LARGE') {
372        //            $html .= "<textarea name='$new_substring_name' cols='60' rows='3'></textarea>\n";
373        //        } else {
374        //            $html .= "<input type='text' name='$new_substring_name' size='44' value=''>\n";
375        //        }
376        //
377        //        $html .= "<div class='admin_element_data' id='langstrings_".$this->id."_substring_new_language_section'>\n";
378        //        $html .= "<img src='" . BASE_SSL_PATH . "images/icons/language.gif' id='langstrings_".$this->id."_substring_new_language_section_image' class='admin_section_button' alt='"._("Choose language")."' title='"._("Choose language")."'>";
379        //        $html .= $liste_languages->GenererFormSelect($locale, "langstrings_".$this->id."_substring_new_language", null, TRUE);
380        //        $html .= "</div>\n";
381        //
382        //        $html .= "</div>\n";
383        //        $html .= "<div class='admin_element_tools'>\n";
384        //
385        //        $new_substring_submit_name = "langstrings_".$this->id."_add_new_entry";
386        //
387        //        // Add string button
388        //        $html .= "<input type='image' name='$new_substring_submit_name' class='admin_section_button' src='" . BASE_SSL_PATH . "images/icons/add.gif' alt='"._("Add new string")."' title='"._("Add new string")."'>";
389        //
390        //        $html .= "</div>\n";
391        //        $html .= "</li>\n";
392        //
393        //        $html .= "</ul>\n";
394        //        $html .= "</div>\n";
395
396        //Nouvelles chaîne
397        $html .= "<li class='admin_element_item_container'>\n";
398        $userData['excludeArray'] = $exclude_array;
399        $userData['typeInterface'] = $type_interface;
400        $html .= self::getNewUI($this->id, $userData);
401        $html .= "</li>\n";
402        $html .= "</ul>\n";
403
404        return parent :: getAdminUI($html, $title);
405    }
406
407    /**
408     * Processes the input of the administration interface for Langstring
409     *
410     * @return void
411     */
412    public function processAdminUI() {
413        // Init values.
414        $result = null;
415
416        if ($this->isOwner(User :: getCurrentUser()) || User :: getCurrentUser()->isSuperAdmin()) {
417            parent :: processAdminUI();
418            $generateur_form_select = new FormSelectGenerator();
419            $sql = "SELECT * FROM content_langstring_entries WHERE content_langstring_entries.langstrings_id = '$this->id'";
420            $this->mBd->execSql($sql, $result, false);
421
422            if ($result != null) {
423                while (list ($key, $value) = each($result)) {
424                    $language = $generateur_form_select->getResult("langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_language", null);
425
426                    if (empty ($language)) {
427                        $language = '';
428                        $languageSQL = 'NULL';
429                    } else {
430                        $languageSQL = "'" . $language . "'";
431                    }
432
433                    if (!empty ($_REQUEST["langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_erase"]) && $_REQUEST["langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_erase"] == true) {
434                        $this->mBd->execSqlUpdate("DELETE FROM content_langstring_entries WHERE langstrings_id = '$this->id' AND langstring_entries_id='$value[langstring_entries_id]'", FALSE);
435
436                        // Create new cache object.
437                        $_cache = new Cache('langstrings_' . $this->id . '_substring_' . $language . '_string', $this->id);
438
439                        // Check if caching has been enabled.
440                        if ($_cache->isCachingEnabled) {
441                            // Remove old cached data.
442                            $_cache->eraseCachedData();
443                        }
444                    } else {
445                        // Strip HTML tags !
446                        $string = $_REQUEST["langstrings_" . $this->id . "_substring_$value[langstring_entries_id]_string"];
447                        $string = $this->mBd->escapeString(strip_tags($string, $this->allowed_html_tags));
448
449                        // If PEAR::HTML_Safe is available strips down all potentially dangerous content
450                        $_HtmlSafe = new HtmlSafe();
451
452                        if ($_HtmlSafe->isHtmlSafeEnabled) {
453                            // Add "embed" and "object" to the default set of dangerous tags
454                            $_HtmlSafe->setDeleteTags(array (
455                            "embed",
456                            "object"
457                            ), true);
458
459                            // Strip HTML
460                            $string = $_HtmlSafe->parseHtml($string);
461                        }
462                        if ($value['value'] != $string || $language!=$value['locales_id']) {
463                            $this->mBd->execSqlUpdate("UPDATE content_langstring_entries SET locales_id = $languageSQL , value = '$string' WHERE langstrings_id = '$this->id' AND langstring_entries_id='$value[langstring_entries_id]'", FALSE);
464                            $this->touch();
465                            // Create new cache object.
466                            $_cache = new Cache('langstrings_' . $this->id . '_substring_' . $language . '_string', $this->id);
467
468                            // Check if caching has been enabled.
469                            if ($_cache->isCachingEnabled) {
470                                // Remove old cached data.
471                                $_cache->eraseCachedData();
472
473                                // Save data into cache.
474                                $_cache->saveCachedData($string);
475                            }
476                        }
477                    }
478                }
479            }
480
481            //Nouvelles chaîne(s)
482            self::processNewUI($this->id, false);
483
484
485        }
486    }
487
488    /**
489     * Retreives the user interface of this object.
490     *
491     * Anything that overrides this method should call the parent method with
492     * it's output at the END of processing.
493     * @return string The HTML fragment for this interface
494     */
495    public function getUserUI() {
496        // Init values
497        $html = '';
498        $html .= $this->getString();
499        /* Handle hyperlink clicktrough logging */
500        $html = $this->replaceHyperLinks($html);
501        $this->setUserUIMainDisplayContent($html);
502        return parent :: getUserUI();
503    }
504
505    /**
506     * Reloads the object from the database.
507     *
508     * Should normally be called after a set operation.
509     *
510     * This function is private because calling it from a subclass will call
511     * the constructor from the wrong scope
512     *
513     * @return void
514
515     */
516    private function refresh() {
517        $this->__construct($this->id);
518    }
519
520    /**
521     * Deletes a Langstring object
522     *
523     * @param string $errmsg Reference to error message
524     *
525     * @return bool True if deletion was successful
526     * @internal Persistent content will not be deleted
527     */
528    public function delete(& $errmsg) {
529        // Init values.
530        $_retval = false;
531
532        if ($this->isPersistent()) {
533            $errmsg = _("Content is persistent (you must make it non persistent before you can delete it)");
534        } else {
535            $db = AbstractDb :: getObject();
536
537            if ($this->isOwner(User :: getCurrentUser()) || User :: getCurrentUser()->isSuperAdmin()) {
538                $_sql = "DELETE FROM content WHERE content_id='$this->id'";
539                $db->execSqlUpdate($_sql, false);
540                $_retval = true;
541
542                // Create new cache object.
543                $_cache = new Cache('all', $this->id);
544
545                // Check if caching has been enabled.
546                if ($_cache->isCachingEnabled) {
547                    // Remove old cached data.
548                    $_cache->eraseCachedGroupData();
549                }
550            } else {
551                $errmsg = _("Access denied (not owner of content)");
552            }
553        }
554
555        return $_retval;
556    }
557
558}
559
560/*
561 * Local variables:
562 * tab-width: 4
563 * c-basic-offset: 4
564 * c-hanging-comment-ender-p: nil
565 * End:
566 */
Note: See TracBrowser for help on using the browser.