/** * list images * * @param resource the SQL result * @return string the rendered text * * @see layouts/layout.php **/ function layout($result) { global $context; // empty list if (!SQL::count($result)) { $output = array(); return $output; } // we return an array of ($url => $attributes) $items = array(); // process all items in the list while ($item = SQL::fetch($result)) { // get the anchor for this image if ($item['anchor']) { $anchor = Anchors::get($item['anchor']); } // url to view the image $url = $context['url_to_home'] . $context['url_to_root'] . Images::get_url($item['id']); // time of last update $time = SQL::strtotime($item['edit_date']); // the title as the label if ($item['title']) { $label = ucfirst($item['title']) . ' (' . $item['image_name'] . ')'; } else { $label = $item['image_name']; } // the section $section = ''; if (is_object($anchor)) { $section = ucfirst($anchor->get_title()); } // the author(s) is an e-mail address, according to rss 2.0 spec $author = $item['create_address'] . ' (' . $item['create_name'] . ')'; if ($item['create_address'] != $item['edit_address']) { if ($author) { $author .= ', '; } $author .= $item['edit_address'] . ' (' . $item['edit_name'] . ')'; } // the description $description = Codes::beautify($item['description']); // cap the number of words $description = Skin::cap($description, 300); // fix image references $description = preg_replace('#"/([^">]+?)"#', '"' . $context['url_to_home'] . '/$1"', $description); $introduction = $description; // other rss fields $extensions = array(); // url for enclosure $type = Files::get_mime_type($item['image_name']); $extensions[] = '<enclosure url="' . $context['url_to_home'] . $context['url_to_root'] . Files::get_path($item['anchor'], 'images') . '/' . $item['image_name'] . '"' . ' length="' . $item['image_size'] . '"' . ' type="' . $type . '" />'; // list all components for this item $items[$url] = array($time, $label, $author, $section, NULL, $introduction, $description, $extensions); } // end of processing SQL::free($result); return $items; }
/** * list dates * * @param resource the SQL result * @return string the rendered text * * @see layouts/layout.php **/ function layout($result) { global $context; // build the calendar $text = 'BEGIN:VCALENDAR' . CRLF . 'VERSION:2.0' . CRLF . 'PRODID:YACS' . CRLF . 'METHOD:PUBLISH' . CRLF; // organization, if any if (isset($context['site_name']) && $context['site_name']) { $text .= 'X-WR-CALNAME:' . $context['site_name'] . CRLF; } // process all items in the list while ($item = SQL::fetch($result)) { // one event at a time $text .= 'BEGIN:VEVENT' . CRLF; // the event spans limited time if (isset($item['duration']) && $item['duration']) { $text .= 'DTSTART:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($item['date_stamp'])) . CRLF; $text .= 'DTEND:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($item['date_stamp']) + $item['duration'] * 60) . CRLF; // a full-day event } else { $text .= 'DTSTART;VALUE=DATE:' . date('Ymd', SQL::strtotime($item['date_stamp'])) . CRLF; $text .= 'DTEND;VALUE=DATE:' . date('Ymd', SQL::strtotime($item['date_stamp']) + 86400) . CRLF; } // url to view the date $text .= 'URL:' . Articles::get_permalink($item) . CRLF; // organization, if any if (isset($item['introduction']) && $item['introduction']) { $text .= 'DESCRIPTION:' . str_replace(array("\n", "\r"), ' ', strip_tags($item['introduction'])) . CRLF; } // build a valid title if (isset($item['title']) && $item['title']) { $text .= 'SUMMARY:' . Codes::beautify_title($item['title']) . CRLF; } // required by Outlook 2003 if (isset($item['id']) && $item['id']) { $text .= 'UID:' . $item['id'] . CRLF; } // date of creation if (isset($item['create_date']) && $item['create_date']) { $text .= 'CREATED:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($item['create_date'])) . CRLF; } // date of last modification if (isset($item['edit_date']) && $item['edit_date']) { $text .= 'DTSTAMP:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($item['edit_date'])) . CRLF; } // next event $text .= 'SEQUENCE:0' . CRLF . 'END:VEVENT' . CRLF; } // date of last update $text .= 'END:VCALENDAR' . CRLF; // end of processing SQL::free($result); return $text; }
/** * list links * * @param resource the SQL result * @return array of resulting items, or NULL * * @see layouts/layout.php **/ function layout($result) { global $context; // we return an array of ($url => $attributes) $items = array(); // empty list if (!SQL::count($result)) { return $items; } // process all items in the list while ($item = SQL::fetch($result)) { // get the anchor for this link if ($item['anchor']) { $anchor = Anchors::get($item['anchor']); } // url is the link itself $url = $item['link_url']; // time of last update $time = SQL::strtotime($item['edit_date']); // the title as the label if ($item['title']) { $label = $item['title']; } else { $label = $url; } // the section $section = ''; if (is_object($anchor)) { $section = ucfirst($anchor->get_title()); } // the author(s) is an e-mail address, according to rss 2.0 spec $author = $item['edit_address'] . ' (' . $item['edit_name'] . ')'; // the description $description = Codes::beautify($item['description']); // cap the number of words $description = Skin::cap($description, 300); // fix image references $description = preg_replace('#"/([^">]+?)"#', '"' . $context['url_to_home'] . '/$1"', $description); $introduction = $description; // other rss fields $extensions = array(); // list all components for this item $items[$url] = array($time, $label, $author, $section, NULL, $introduction, $description, $extensions); } // end of processing SQL::free($result); return $items; }
/** * list users * * @param resource the SQL result * @return string the rendered text * * @see layouts/layout.php **/ function layout($result) { global $context; // empty list if (!SQL::count($result)) { $output = array(); return $output; } // we return an array of ($url => $attributes) $items = array(); // process all items in the list while ($item = SQL::fetch($result)) { // url to view the user profile $url = Users::get_permalink($item); // time of last update $time = SQL::strtotime($item['edit_date']); // item title if ($item['full_name']) { $label = ucfirst(Skin::strip($item['full_name'], 10)); } else { $label = ucfirst(Skin::strip($item['nick_name'], 10)); } // the section $section = ''; // the author(s) is an e-mail address, according to rss 2.0 spec $author .= $item['edit_address'] . ' (' . $item['edit_name'] . ')'; // introduction $introduction = Codes::beautify($item['introduction']); // the description $description = Codes::beautify($item['description']); // cap the number of words $description = Skin::cap($description, 300); // fix image references $description = preg_replace('#"/([^">]+?)"#', '"' . $context['url_to_home'] . '/$1"', $description); // other rss fields $extensions = array(); // list all components for this item $items[$url] = array($time, $label, $author, $section, $icon, $introduction, $description, $extensions); } // end of processing SQL::free($result); return $items; }
/** * remember the last action for this article * * This function is called by related items. What does it do? * - On image creation, the adequate code is added to the description field to let the image be displayed inline * - On icon selection, the icon field is updated * - On thumbnail image selection, the thumbnail image field is updated * - On location creation, some code is inserted in the description field to display location name inline * - On table creation, some code is inserted in the description field to display the table inline * * @see articles/article.php * @see articles/edit.php * @see shared/anchor.php * * @param string one of the pre-defined action code * @param string the id of the item related to this update * @param boolean TRUE to not change the edit date of this anchor, default is FALSE */ function touch($action, $origin = NULL, $silently = FALSE) { global $context; // we make extensive use of comments below include_once $context['path_to_root'] . 'comments/comments.php'; // don't go further on import if (preg_match('/import$/i', $action)) { return; } // no article bound if (!isset($this->item['id'])) { return; } // delegate to overlay if (is_object($this->overlay) && $this->overlay->touch($action, $origin, $silently) === false) { return; // stop on false } // clear floating objects if ($action == 'clear') { $this->item['description'] .= ' [clear]'; $query = "UPDATE " . SQL::table_name('articles') . " SET description='" . SQL::escape($this->item['description']) . "'" . " WHERE id = " . SQL::escape($this->item['id']); SQL::query($query); return; } // get the related overlay, if any if (!isset($this->overlay)) { $this->overlay = NULL; if (isset($this->item['overlay'])) { $this->overlay = Overlay::load($this->item, 'article:' . $this->item['id']); } } // components of the query $query = array(); // a new comment has been posted if ($action == 'comment:create') { // purge oldest comments Comments::purge_for_anchor('article:' . $this->item['id']); // file upload } elseif ($action == 'file:create' || $action == 'file:upload') { // actually, several files have been added $label = ''; if (!$origin) { // only when comments are allowed if (!Articles::has_option('no_comments', $this->anchor, $this->item)) { // remember this as an automatic notification $fields = array(); $fields['anchor'] = 'article:' . $this->item['id']; $fields['description'] = i18n::s('Several files have been added'); $fields['type'] = 'notification'; Comments::post($fields); } // one file has been added } elseif (!Codes::check_embedded($this->item['description'], 'embed', $origin) && ($item = Files::get($origin, TRUE))) { // this file is eligible for being embedded in the page if (isset($item['file_name']) && Files::is_embeddable($item['file_name'])) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { $label = '[embed=' . $origin . ']'; } // else add a comment to take note of the upload } else { // only when comments are allowed if (!Articles::has_option('no_comments', $this->anchor, $this->item)) { // remember this as an automatic notification $fields = array(); $fields['anchor'] = 'article:' . $this->item['id']; if ($action == 'file:create') { $fields['description'] = '[file=' . $item['id'] . ',' . $item['file_name'] . ']'; } else { $fields['description'] = '[download=' . $item['id'] . ',' . $item['file_name'] . ']'; } Comments::post($fields); } } } // we are in some interactive thread if ($origin && $this->has_option('view_as_chat')) { // default is to download the file if (!$label) { $label = '[download=' . $origin . ']'; } // this is the first contribution to the thread if (!($comment = Comments::get_newest_for_anchor('article:' . $this->item['id']))) { $fields = array(); $fields['anchor'] = 'article:' . $this->item['id']; $fields['description'] = $label; // this is a continuated contribution from this authenticated surfer } elseif ($comment['type'] != 'notification' && Surfer::get_id() && (isset($comment['create_id']) && Surfer::get_id() == $comment['create_id'])) { $comment['description'] .= BR . $label; $fields = $comment; // else process the contribution as a new comment } else { $fields = array(); $fields['anchor'] = 'article:' . $this->item['id']; $fields['description'] = $label; } // only when comments are allowed if (!Articles::has_option('no_comments', $this->anchor, $this->item)) { Comments::post($fields); } // include flash videos in a regular page } elseif ($origin && $label) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' ' . $label) . "'"; } // suppress references to a deleted file } elseif ($action == 'file:delete' && $origin) { // suppress reference in main description field $text = Codes::delete_embedded($this->item['description'], 'download', $origin); $text = Codes::delete_embedded($text, 'embed', $origin); $text = Codes::delete_embedded($text, 'file', $origin); // save changes $query[] = "description = '" . SQL::escape($text) . "'"; // append a reference to a new image to the description } elseif ($action == 'image:create' && $origin) { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { // list has already started if (preg_match('/\\[image=[^\\]]+?\\]\\s*$/', $this->item['description'])) { $this->item['description'] .= ' [image=' . $origin . ']'; } else { $this->item['description'] .= "\n\n" . '[image=' . $origin . ']'; } $query[] = "description = '" . SQL::escape($this->item['description']) . "'"; } } // also use it as thumnail if none has been defined yet if (!isset($this->item['thumbnail_url']) || !trim($this->item['thumbnail_url'])) { include_once $context['path_to_root'] . 'images/images.php'; if (($image = Images::get($origin)) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } // refresh stamp only if image update occurs within 6 hours after last edition if (SQL::strtotime($this->item['edit_date']) + 6 * 60 * 60 < time()) { $silently = TRUE; } // suppress a reference to an image that has been deleted } elseif ($action == 'image:delete' && $origin) { // suppress reference in main description field $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'image', $origin)) . "'"; // suppress references as icon and thumbnail as well include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } if ($url = Images::get_thumbnail_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } } // set an existing image as the article icon } elseif ($action == 'image:set_as_icon' && $origin) { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { $query[] = "icon_url = '" . SQL::escape($url) . "'"; } // also use it as thumnail if none has been defined yet if (!(isset($this->item['thumbnail_url']) && trim($this->item['thumbnail_url'])) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } // set an existing image as the article thumbnail } elseif ($action == 'image:set_as_thumbnail' && $origin) { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { // use the thumbnail for large files, or the image itself for smaller files if ($image['image_size'] > $context['thumbnail_threshold']) { $url = Images::get_thumbnail_href($image); } else { $url = Images::get_icon_href($image); } $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } elseif ($origin) { $query[] = "thumbnail_url = '" . SQL::escape($origin) . "'"; } // do not remember minor changes $silently = TRUE; // append a new image, and set it as the article thumbnail } elseif ($action == 'image:set_as_both' && $origin) { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { // use the thumbnail for large files, or the image itself for smaller files if ($image['image_size'] > $context['thumbnail_threshold']) { $url = Images::get_thumbnail_href($image); } else { $url = Images::get_icon_href($image); } $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } elseif ($origin) { $query[] = "thumbnail_url = '" . SQL::escape($origin) . "'"; } // do not remember minor changes $silently = TRUE; // add a reference to a location in the article description } elseif ($action == 'location:create' && $origin) { if (!Codes::check_embedded($this->item['description'], 'location', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [location=' . $origin . ']') . "'"; } // suppress a reference to a location that has been deleted } elseif ($action == 'location:delete' && $origin) { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'location', $origin)) . "'"; // add a reference to a new table in the article description } elseif ($action == 'table:create' && $origin) { if (!Codes::check_embedded($this->item['description'], 'table', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . "\n" . '[table=' . $origin . ']' . "\n") . "'"; } // suppress a reference to a table that has been deleted } elseif ($action == 'table:delete' && $origin) { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'table', $origin)) . "'"; } // stamp the update if (!$silently) { $query[] = "edit_name='" . SQL::escape(Surfer::get_name()) . "'," . "edit_id=" . SQL::escape(Surfer::get_id()) . "," . "edit_address='" . SQL::escape(Surfer::get_email_address()) . "'," . "edit_action='" . SQL::escape($action) . "'," . "edit_date='" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'"; } // update the database if (count($query)) { $query = "UPDATE " . SQL::table_name('articles') . " SET " . implode(', ', $query) . " WHERE id = " . SQL::escape($this->item['id']); SQL::query($query); } // add this page to the watch list of the contributor, on any action if (Surfer::get_id()) { Members::assign('article:' . $this->item['id'], 'user:'******'article:' . $this->item['id'], $this->item['active']); // always clear the cache, even on no update Articles::clear($this->item); // get the parent if (!$this->anchor) { $this->anchor = Anchors::get($this->item['anchor']); } // propagate the touch upwards if (is_object($this->anchor)) { $this->anchor->touch('article:update', $this->item['id'], TRUE); } }
/** * remember the last action for this section * * @see articles/article.php * @see shared/anchor.php * * @param string the description of the last action * @param string the id of the item related to this update * @param boolean TRUE to not change the edit date of this anchor, default is FALSE */ function touch($action, $origin = NULL, $silently = FALSE) { global $context; // we make extensive use of comments below include_once $context['path_to_root'] . 'comments/comments.php'; // don't go further on import if (preg_match('/import$/i', $action)) { return; } // no section bound if (!isset($this->item['id'])) { return; } // delegate to overlay if (is_object($this->overlay) && $this->overlay->touch($action, $origin, $silently) === false) { return; // stop on false } // sanity check if (!$origin) { logger::remember('sections/section.php: unexpected NULL origin at touch()'); return; } // components of the query $query = array(); // a new page has been added to the section if ($action == 'article:publish' || $action == 'article:submit') { // limit the number of items attached to this section if (isset($this->item['maximum_items']) && $this->item['maximum_items'] > 10) { Articles::purge_for_anchor('section:' . $this->item['id'], $this->item['maximum_items']); } // a new comment has been posted } elseif ($action == 'comment:create') { // purge oldest comments Comments::purge_for_anchor('section:' . $this->item['id']); // file upload } elseif ($action == 'file:create' || $action == 'file:upload') { // actually, several files have been added $label = ''; if (!$origin) { $fields = array(); $fields['anchor'] = 'section:' . $this->item['id']; $fields['description'] = i18n::s('Several files have been added'); $fields['type'] = 'notification'; Comments::post($fields); // one file has been added } elseif (!Codes::check_embedded($this->item['description'], 'embed', $origin) && ($item = Files::get($origin, TRUE))) { // this file is eligible for being embedded in the page if (isset($item['file_name']) && Files::is_embeddable($item['file_name'])) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { $label = '[embed=' . $origin . ']'; } // else add a comment to take note of the upload } elseif (Comments::allow_creation($this->item, null, 'section')) { $fields = array(); $fields['anchor'] = 'section:' . $this->item['id']; if ($action == 'file:create') { $fields['description'] = '[file=' . $item['id'] . ',' . $item['file_name'] . ']'; } else { $fields['description'] = '[download=' . $item['id'] . ',' . $item['file_name'] . ']'; } Comments::post($fields); } } // include flash videos in a regular page if ($label) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' ' . $label) . "'"; } // suppress references to a deleted file } elseif ($action == 'file:delete') { // suppress reference in main description field $text = Codes::delete_embedded($this->item['description'], 'download', $origin); $text = Codes::delete_embedded($text, 'embed', $origin); $text = Codes::delete_embedded($text, 'file', $origin); // save changes $query[] = "description = '" . SQL::escape($text) . "'"; // append a reference to a new image to the description } elseif ($action == 'image:create') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { // list has already started if (preg_match('/\\[image=[^\\]]+?\\]\\s*$/', $this->item['description'])) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } else { $query[] = "description = '" . SQL::escape($this->item['description'] . "\n\n" . '[image=' . $origin . ']') . "'"; } } } // also use it as thumnail if none has been defined yet if (!isset($this->item['thumbnail_url']) || !trim($this->item['thumbnail_url'])) { include_once $context['path_to_root'] . 'images/images.php'; if (($image = Images::get($origin)) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } // refresh stamp only if image update occurs within 6 hours after last edition if (SQL::strtotime($this->item['edit_date']) + 6 * 60 * 60 < time()) { $silently = TRUE; } // suppress a reference to an image that has been deleted } elseif ($action == 'image:delete') { // suppress reference in main description field $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'image', $origin)) . "'"; // suppress references as icon and thumbnail as well include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } if ($url = Images::get_thumbnail_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } } // set an existing image as the section icon } elseif ($action == 'image:set_as_icon') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { $query[] = "icon_url = '" . SQL::escape($url) . "'"; } // also use it as thumnail if none has been defined yet if (!(isset($this->item['thumbnail_url']) && trim($this->item['thumbnail_url'])) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } elseif ($origin) { $query[] = "icon_url = '" . SQL::escape($origin) . "'"; } $silently = TRUE; // set an existing image as the section thumbnail } elseif ($action == 'image:set_as_thumbnail') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { // use the thumbnail for large files, or the image itself for smaller files if ($image['image_size'] > $context['thumbnail_threshold']) { $url = Images::get_thumbnail_href($image); } else { $url = Images::get_icon_href($image); } $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } elseif ($origin) { $query[] = "thumbnail_url = '" . SQL::escape($origin) . "'"; } $silently = TRUE; // append a new image, and set it as the article thumbnail } elseif ($action == 'image:set_as_both') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { // use the thumbnail for large files, or the image itself for smaller files if ($image['image_size'] > $context['thumbnail_threshold']) { $url = Images::get_thumbnail_href($image); } else { $url = Images::get_icon_href($image); } $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } elseif ($origin) { $query[] = "thumbnail_url = '" . SQL::escape($origin) . "'"; } // do not remember minor changes $silently = TRUE; // add a reference to a new table in the section description } elseif ($action == 'table:create') { if (!Codes::check_embedded($this->item['description'], 'table', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [table=' . $origin . ']') . "'"; } // suppress a reference to a table that has been deleted } elseif ($action == 'table:delete') { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'table', $origin)) . "'"; } // stamp the update if (!$silently) { $query[] = "edit_name='" . SQL::escape(Surfer::get_name()) . "'," . "edit_id=" . SQL::escape(Surfer::get_id()) . "," . "edit_address='" . SQL::escape(Surfer::get_email_address()) . "'," . "edit_action='{$action}'," . "edit_date='" . SQL::escape(gmstrftime('%Y-%m-%d %H:%M:%S')) . "'"; } // update the database if (@count($query)) { $query = "UPDATE " . SQL::table_name('sections') . " SET " . implode(', ', $query) . " WHERE id = " . SQL::escape($this->item['id']); SQL::query($query); } // always clear the cache, even on no update Sections::clear($this->item); // get the parent if (!$this->anchor) { $this->anchor = Anchors::get($this->item['anchor']); } // propagate the touch upwards silently -- we only want to purge the cache if (is_object($this->anchor)) { $this->anchor->touch('section:touch', $this->item['id'], TRUE); } }
/** * wait for updates * * This script will wait for new updates before providing them to caller. * Because of potential time-outs, you have to care of retries. * * @param string reference to thread (e.g., 'article:123') * @param string timestamp of previous update * @return array attributes including new comments and a timestamp * * @see articles/view_as_chat.php * @see comments/thread.php */ public static function &pull($anchor, $stamp, $count = 100) { global $context; $timer = 1; // some implementations will kill network connections earlier anyway Safe::set_time_limit(max(30, $timer)); // we return formatted text $text = ''; // sanity check if (!$anchor) { return $text; } // the query to get time of last update $query = "SELECT edit_date, edit_name FROM " . SQL::table_name('comments') . " AS comments " . " WHERE comments.anchor LIKE '" . SQL::escape($anchor) . "'" . " ORDER BY comments.edit_date DESC" . " LIMIT 1"; // we may timeout ourself, to be safe with network resources while (!($stat = SQL::query_first($query)) || isset($stat['edit_date']) && $stat['edit_date'] <= $stamp) { // kill the request to avoid repeated transmissions when nothing has changed if (--$timer < 1) { http::no_content(); die; } // preserve server resources sleep(1); } // return an array of variables $response = array(); $response['items'] =& Comments::list_by_thread_for_anchor($anchor, 0, $count, 'thread'); $response['name'] = strip_tags($stat['edit_name']); $response['timestamp'] = SQL::strtotime($stat['edit_date']); // return by reference return $response; }
$details[] = EXPIRED_FLAG . ' ' . sprintf(i18n::s('Category has expired %s'), Skin::build_date($item['expiry_date'])); } // display details, if any if (count($details)) { $context['page_details'] .= ucfirst(implode(BR, $details)) . BR; } // other details $details = array(); // additional details for associates and editors if (Surfer::is_associate() || is_object($anchor) && $anchor->is_assigned()) { // the creator of this category if ($item['create_date']) { $details[] = sprintf(i18n::s('posted by %s %s'), Users::get_link($item['create_name'], $item['create_address'], $item['create_id']), Skin::build_date($item['create_date'])); } // hide last edition if done by creator, and if less than 24 hours between creation and last edition if ($item['create_date'] && $item['create_id'] == $item['edit_id'] && SQL::strtotime($item['create_date']) + 24 * 60 * 60 >= SQL::strtotime($item['edit_date'])) { } else { if ($item['edit_action']) { $action = Anchors::get_action_label($item['edit_action']); } else { $action = i18n::s('edited'); } $details[] = sprintf(i18n::s('%s by %s %s'), $action, Users::get_link($item['edit_name'], $item['edit_address'], $item['edit_id']), Skin::build_date($item['edit_date'])); } // the number of hits if ($item['hits'] > 1) { $details[] = Skin::build_number($item['hits'], i18n::s('hits')); } // rank for this section if (intval($item['rank']) != 10000 && Surfer::is_associate()) { $details[] = '{' . $item['rank'] . '}';
/** * list articles * * @param resource the SQL result * @return array * * @see layouts/layout.php **/ function layout($result) { global $context; // we return an array of ($url => $attributes) $items = array(); // empty list if (!SQL::count($result)) { return $items; } // process all items in the list while ($item = SQL::fetch($result)) { // get the related overlay, if any $overlay = Overlay::load($item, 'category:' . $item['id']); // get the anchor $anchor = Anchors::get($item['anchor']); // provide an absolute link $url = Categories::get_permalink($item); // build a title if (is_object($overlay)) { $title = Codes::beautify_title($overlay->get_text('title', $item)); } else { $title = Codes::beautify_title($item['title']); } // time of last update $time = SQL::strtotime($item['edit_date']); // the section $root = ''; if ($item['anchor'] && ($anchor = Anchors::get($item['anchor']))) { $root = ucfirst(trim(strip_tags(Codes::beautify_title($anchor->get_title())))); } // the icon to use $icon = ''; if ($item['thumbnail_url']) { $icon = $item['thumbnail_url']; } elseif ($item['anchor'] && ($anchor = Anchors::get($item['anchor'])) && is_callable($anchor, 'get_bullet_url')) { $icon = $anchor->get_bullet_url(); } if ($icon) { $icon = $context['url_to_home'] . $context['url_to_root'] . $icon; } // the author(s) is an e-mail address, according to rss 2.0 spec $author = ''; if (isset($item['create_address'])) { $author .= $item['create_address']; } if (isset($item['create_name']) && trim($item['create_name'])) { $author .= ' (' . $item['create_name'] . ')'; } if (isset($item['edit_address']) && trim($item['edit_address']) && $item['create_address'] != $item['edit_address']) { if ($author) { $author .= ', '; } $author .= $item['edit_address']; if (isset($item['edit_name']) && trim($item['edit_name'])) { $author .= ' (' . $item['edit_name'] . ')'; } } // list all components for this item $items[$url] = array($time, $title, $author, $root, $icon, '', '', ''); } // end of processing SQL::free($result); return $items; }
// phone number, if any if (isset($agent['phone_number']) && $agent['phone_number']) { $text .= 'TEL;PREF:' . $agent['phone_number'] . "\r\n"; } // alternate number, if any if (isset($agent['alternate_number']) && $agent['alternate_number']) { $text .= 'TEL;MSG:' . $agent['alternate_number'] . "\r\n"; } // web mail, if any if (isset($agent['email']) && $agent['email']) { $text .= 'EMAIL;PREF;INTERNET:' . $agent['email'] . "\r\n"; } $text .= 'END:VCARD' . "\r\n"; } // date of last update $text .= 'REV:' . date('Ymd\\THis\\Z', SQL::strtotime($item['edit_date'])) . CRLF . 'END:VCARD' . CRLF; // // transfer to the user agent // // no encoding, no compression and no yacs handler... if (!headers_sent()) { Safe::header('Content-Type: text/x-vcard'); Safe::header('Content-Transfer-Encoding: binary'); Safe::header('Content-Length: ' . strlen($text)); } // suggest a download if (!headers_sent()) { $file_name = utf8::to_ascii(Skin::strip($context['page_title']) . '.vcf'); Safe::header('Content-Disposition: attachment; filename="' . str_replace('"', '', $file_name) . '"'); } // enable 30-minute caching (30*60 = 1800), even through https, to help IE6 on download
/** * process deferred messages * * Most often, the server has to stay below a given rate of messages, * for example 50 messages per hour. * * Of course, any lively community will feature bursts of activity and of * messages, therefore the need for a shaping mechanism. * * YACS implements a leaking bucket algorithm to take care of messages sent * previously: * * 1. Initially, the bucket is empty. * * 2. New messages are queued in the database, to be processed asynchronously. * * 3. On background ticks, the bucket is decremented. If the bucket becomes * empty, and if some messages have been queued, a couple of them are sent, and * the bucket is incremented accordingly. * * Bucket content is managed as value 'bucket.content' saved in the database. * * The bucket size is given by parameter $context['mail_hourly_maximum'], set * in the configuration panel for system parameters. * * This parameter has a default value of 50, meaning YACS will not send more * than 50 messages per hour. * * Background processing is either added to regular page generation or delegated * to an external sub-system (e.g., cron). In case of a large site, we recommend * to use the second solution, even if this adds additional setup steps. Your * choice will be recorded in the configuration panel for system parameters. * * @see control/configure.php * * The number of messages sent on each tick can go up to the bucket size if * background processing is external. Else it is one fourth of bucket size, to * minimize impact on watching surfer. * * @see cron.php */ public static function tick_hook() { global $context; // email services have to be activated if (!isset($context['with_email']) || $context['with_email'] != 'Y') { return; } // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $start = get_micro_time(); // get bucket size --force it if set to 0 if (!isset($context['mail_hourly_maximum']) || $context['mail_hourly_maximum'] < 5) { $context['mail_hourly_maximum'] = 50; } // get record related to last tick include_once $context['path_to_root'] . 'shared/values.php'; $bucket = Values::get_record('mailer.bucket.content', 0); $bucket['value'] = intval($bucket['value']); // some content to leak if ($bucket['value'] > 0) { // date of last stamp if (isset($bucket['edit_date'])) { $stamp = SQL::strtotime($bucket['edit_date']); } else { $stamp = time() - 3600; } // leak is maximum after one hour $leak = intval($context['mail_hourly_maximum'] * (time() - $stamp) / 3600); // preserve previous value until actual leak if ($leak < 1) { return; } // actual leak $bucket['value'] = max(0, $bucket['value'] - $leak); } // process some messages only when bucket is empty $count = 0; if ($bucket['value'] < 1) { // reduced speed if on-line processing if (isset($_SERVER['REMOTE_ADDR'])) { $slice = intval($context['mail_hourly_maximum'] / 4); } else { $slice = intval($context['mail_hourly_maximum']); } // get some messages, if any $query = "SELECT * FROM " . SQL::table_name('messages') . " ORDER BY edit_date LIMIT 0, " . $slice; if ($result = SQL::query($query)) { // process every message while ($item = SQL::fetch($result)) { Mailer::process($item['recipient'], $item['subject'], $item['message'], $item['headers']); // purge the queue $query = 'DELETE FROM ' . SQL::table_name('messages') . ' WHERE id = ' . $item['id']; SQL::query($query); // fill the bucket $bucket['value'] += 1; $count++; // take care of time if (!($count % 50)) { // ensure enough execution time Safe::set_time_limit(30); } } // close connection Mailer::close(); } } // remember new state of the bucket Values::set('mailer.bucket.content', $bucket['value']); // compute execution time $time = round(get_micro_time() - $start, 2); // report on work achieved if ($count > 1) { return 'shared/mailer.php: ' . $count . ' messages have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'shared/mailer.php: 1 message has been processed (' . $time . ' seconds)' . BR; } elseif ($bucket['value']) { return 'shared/mailer.php: delaying messages (' . $time . ' seconds)' . BR; } else { return 'shared/mailer.php: nothing to do (' . $time . ' seconds)' . BR; } }
/** * remember the last action for this user * * @param string the description of the last action * @param string the id of the item related to this update * @param boolean TRUE to not change the edit date of this anchor, default is FALSE * * @see shared/anchor.php */ function touch($action, $origin = NULL, $silently = FALSE) { global $context; // don't go further on import if (preg_match('/import$/i', $action)) { return; } // no item bound if (!isset($this->item['id'])) { return; } // sanity check if (!$origin) { logger::remember('users/user.php: unexpected NULL origin at touch()'); return; } // components of the query $query = array(); // append a reference to a new image to the description if ($action == 'image:create') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { // list has already started if (preg_match('/\\[image=[^\\]]+?\\]\\s*$/', $this->item['description'])) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } else { $query[] = "description = '" . SQL::escape($this->item['description'] . "\n\n" . '[image=' . $origin . ']') . "'"; } } } // refresh stamp only if image update occurs within 6 hours after last edition if (SQL::strtotime($this->item['edit_date']) + 6 * 60 * 60 < time()) { $silently = TRUE; } // suppress a reference to an image that has been deleted } elseif ($action == 'image:delete') { // suppress reference in main description field $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'image', $origin)) . "'"; // suppress references as icon and thumbnail as well include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { if ($this->item['avatar_url'] == $url) { $query[] = "avatar_url = ''"; } } if ($url = Images::get_thumbnail_href($image)) { if ($this->item['avatar_url'] == $url) { $query[] = "avatar_url = ''"; } } } // set an existing image as the user avatar } elseif ($action == 'image:set_as_avatar') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { $query[] = "avatar_url = '" . SQL::escape($url) . "'"; } } $silently = TRUE; // set an existing image as the user thumbnail } elseif ($action == 'image:set_as_thumbnail') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_thumbnail_href($image)) { $query[] = "avatar_url = '" . SQL::escape($url) . "'"; } } $silently = TRUE; // append a new image } elseif ($action == 'image:set_as_both') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } // do not remember minor changes $silently = TRUE; // add a reference to a location in the article description } elseif ($action == 'location:create') { if (!Codes::check_embedded($this->item['description'], 'location', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [location=' . $origin . ']') . "'"; } // suppress a reference to a location that has been deleted } elseif ($action == 'location:delete') { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'location', $origin)) . "'"; // add a reference to a new table in the user description } elseif ($action == 'table:create') { if (!Codes::check_embedded($this->item['description'], 'table', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [table=' . $origin . ']') . "'"; } // suppress a reference to a table that has been deleted } elseif ($action == 'table:delete') { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'table', $origin)) . "'"; } // stamp the update if (!$silently) { $query[] = "edit_name='" . SQL::escape(Surfer::get_name()) . "'," . "edit_id=" . SQL::escape(Surfer::get_id()) . "," . "edit_address='" . SQL::escape(Surfer::get_email_address()) . "'," . "edit_action='{$action}'," . "edit_date='" . SQL::escape(gmstrftime('%Y-%m-%d %H:%M:%S')) . "'"; } // clear the cache for users, even for minor updates (e.g., image deletion) Users::clear($this->item); // ensure we have a valid update query if (!@count($query)) { return; } // update the anchor user $query = "UPDATE " . SQL::table_name('users') . " SET " . implode(', ', $query) . " WHERE id = " . SQL::escape($this->item['id']); SQL::query($query, FALSE, $context['users_connection']); }
} elseif (isset($_REQUEST['anchor'])) { $link = 'articles/edit.php?anchor=' . $_REQUEST['anchor']; } else { $link = 'articles/edit.php'; } Safe::redirect($context['url_to_home'] . $context['url_to_root'] . 'users/login.php?url=' . urlencode($link)); } // permission denied to authenticated user Safe::header('Status: 401 Unauthorized', TRUE, 401); Logger::error(i18n::s('You are not allowed to perform this operation.')); // an error occured } elseif (count($context['error'])) { $item = $_REQUEST; $with_form = TRUE; // page has been assigned to another person during the last 5 minutes } elseif (isset($item['assign_id']) && $item['assign_id'] && !Surfer::is($item['assign_id']) && SQL::strtotime($item['assign_date']) + 5 * 60 >= time()) { // permission denied to authenticated user Safe::header('Status: 401 Unauthorized', TRUE, 401); $context['text'] .= Skin::build_block(sprintf(i18n::s('This page is currently edited by %s. You have to wait for a new version to be released.'), Users::get_link($item['assign_name'], $item['assign_address'], $item['assign_id'])), 'caution'); // process uploaded data } elseif (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') { // protect from hackers if (isset($_REQUEST['edit_name'])) { $_REQUEST['edit_name'] = preg_replace(FORBIDDEN_IN_NAMES, '_', $_REQUEST['edit_name']); } if (isset($_REQUEST['edit_address'])) { $_REQUEST['edit_address'] = encode_link($_REQUEST['edit_address']); } // track anonymous surfers Surfer::track($_REQUEST); // set options
$text .= ' <lastBuildDate>' . gmdate('D, d M Y H:i:s') . ' GMT</lastBuildDate>' . "\n" . ' <generator>yacs</generator>' . "\n" . ' <docs>http://blogs.law.harvard.edu/tech/rss</docs>' . "\n" . ' <ttl>5</ttl>' . "\n"; // list last events $events = Logger::get_tail(50, 'all'); if (is_array($events)) { // the actual list of events foreach ($events as $event) { list($stamp, $surfer, $script, $label, $description) = $event; // formatting patterns $search = array("|\r\n|", "|<br\\s*/>\n+|i", "|\n\n+[ \t]*-\\s+|i", "|\n[ \t]*-\\s+|i", "|\n\n+[ \t]*\\.\\s+|i", "|\n[ \t]*\\.\\s+|i", "|\n\n+[ \t]*\\*\\s+|i", "|\n[ \t]*\\*\\s+|i", "|\n\n+[ \t]*¤\\s+|i", "|\n[ \t]*¤\\s+|i", "|\n\n+[ \t]*\\•\\s+|i", "|\n[ \t]*\\•\\s+|i", "/\n[ \t]*(From|To|cc|bcc|Subject|Date):(\\s*)/i", "|\n[ \t]*>(\\s*)|i", "|\n[ \t]*\\|(\\s*)|i", "#([\n\t ])([a-z]+?)://([^, <>{}\n\r]+)#is", "#^([a-z]+?)://([^, <>{}\n\r]+)#is", "#([\n\t ])www\\.([a-z0-9\\-]+)\\.([a-z0-9\\-.\\~]+)((?:/[^,< \n\r]*)?)#is", "#([\n\t ])([a-z0-9\\-_.]+?)@([^,< \\.\n\r]+\\.[^,< \n\r]+)#is", "|\n\n|i"); $replace = array("\n", BR, BR . BR . "- ", BR . "- ", BR . BR . "- ", BR . "- ", BR . BR . "- ", BR . "- ", BR . BR . "- ", BR . "- ", BR . BR . "• ", BR . "• ", BR . "\$1:\$2", BR . ">\$1", BR . "|\$1", "\$1<a href=\"\$2://\$3\">\$2://\$3</a>", "<a href=\"\$1://\$2\">\$1://\$2</a>", "\$1<a href=\"http://www.\$2.\$3\$4\">http://www.\$2.\$3\$4</a>", "\$1<a href=\"mailto:\$2@\$3\">\$2@\$3</a>", BR . BR); // build an extensive description field $description = nl2br(preg_replace($search, $replace, $description)) . '<p>' . $script . (strlen($surfer) > 1 ? ' for ' . $surfer : '') . ' on ' . $stamp . "</p>"; // build a unique id $id = md5($label . $description . $stamp . $script . $surfer); // output one story $text .= "\n" . ' <item>' . "\n" . ' <title>' . encode_field(strip_tags($label)) . "</title>\n" . ' <description><![CDATA[ ' . $description . " ]]></description>\n" . ' <pubDate>' . gmdate('D, d M Y H:i:s', SQL::strtotime($stamp)) . " GMT</pubDate>\n" . ' <link>' . $context['url_to_home'] . $context['url_to_root'] . 'agents/?subject=events&id=' . $id . "</link>\n" . ' <guid isPermaLink="false">' . $id . "</guid>\n" . ' <category>' . encode_field($script) . "</category>\n" . "\t</item>\n"; } } // the postamble $text .= "\n\t</channel>\n" . '</rss>'; // // transfer to the user agent // // handle the output correctly render_raw('text/xml; charset=' . $context['charset']); // suggest a name on download if (!headers_sent()) { $file_name = $context['site_name'] . '.events.rss.xml'; $file_name =& utf8::to_ascii($file_name); Safe::header('Content-Disposition: inline; filename="' . str_replace('"', '', $file_name) . '"'); }
/** * get date of last modification * * @see services/check.php * * @return array the attribute 'timestamp' contains time of last update */ function &check() { $response = array(); // 'timestamp' if (!isset($this->item['edit_date'])) { $response['timestamp'] = ''; } else { $response['timestamp'] = SQL::strtotime($this->item['edit_date']); } // 'name' if (!isset($this->item['edit_name'])) { $response['name'] = ''; } else { $response['name'] = strip_tags($this->item['edit_name']); } return $response; }
/** * purge idle space * * This function OPTIMIZEs tables that may create overheads because of * frequent deletions, including: cache, links, members, messages, * notifications, values, versions, visits. * * Last purge is recorded as value 'sql.tick'. * * @param boolean optional TRUE to not report on any error * @return a string to be displayed in resulting page, if any */ public static function purge($silent = FALSE) { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $stamp = get_micro_time(); // get date of last tick include_once $context['path_to_root'] . 'shared/values.php'; $record = Values::get_record('sql.tick', NULL_DATE); // wait at least 8 hours = 24*3600 seconds between ticks if (isset($record['edit_date'])) { $target = SQL::strtotime($record['edit_date']) + 8 * 3600; } else { $target = time(); } // request to be delayed if ($target > time()) { return 'shared/sql.php: wait until ' . gmdate('r', $target) . ' GMT' . BR; } // recover unused bytes $query = 'OPTIMIZE TABLE ' . SQL::table_name('cache') . ', ' . SQL::table_name('links') . ', ' . SQL::table_name('members') . ', ' . SQL::table_name('messages') . ', ' . SQL::table_name('notifications') . ', ' . SQL::table_name('values') . ', ' . SQL::table_name('versions') . ', ' . SQL::table_name('visits'); $result = SQL::query($query, $silent); // remember tick date and resulting text Values::set('sql.tick', 'purge'); // compute execution time $time = round(get_micro_time() - $stamp, 2); // report on work achieved if ($result) { return 'shared/sql.php: unused bytes have been recovered (' . $time . ' seconds)' . BR; } else { return 'shared/sql.php: nothing to recover (' . $time . ' seconds)' . BR; } }
// remember tick date and resulting text Values::set('cron.hourly', $context['text']); // log outcome of script execution in debug mode if ($context['with_debug'] == 'Y') { Logger::remember('cron.php: hourly processing', $context['text'], 'debug'); } } // // daily jobs // echo 'Checking daily jobs...' . BR; // get date of last run $record = Values::get_record('cron.daily', NULL_DATE); // wait at least 1 day = 86400 seconds between runs if (isset($record['edit_date'])) { $target = SQL::strtotime($record['edit_date']) + 86400; } else { $target = time(); } // request to be delayed if ($target > time()) { echo 'Wait until ' . gmdate('r', $target) . ' GMT' . BR; } else { Values::set('cron.daily', 'running...'); // do the job and provide feed-back to user $context['text'] = Hooks::include_scripts('daily'); echo $context['text']; // remember tick date and resulting text Values::set('cron.daily', $context['text']); // log outcome of script execution in debug mode if ($context['with_debug'] == 'Y') {
/** * stamp an article * * This function is used to change various dates for one article. * * [*] If a publication date is provided, it is saved along the article. * An optional expiry date will be saved as well. * * [*] If only an expiry date is provided, it is saved along the article. * * [*] If no date is provided, the review field is updated to the current date and time. * * Dates are supposed to be in UTC time zone. * * The name of the surfer is registered as the official publisher. * As an alternative, publisher attributes ('name', 'id' and 'address') can be provided * in parameters. * * @param int the id of the item to publish * @param string the target publication date, if any * @param string the target expiration date, if any * @param array attributes of the publisher, if any * @return string either a null string, or some text describing an error to be inserted into the html response * * @see articles/publish.php * @see sections/manage.php **/ public static function stamp($id, $publication = NULL, $expiry = NULL, $publisher = NULL) { global $context; // id cannot be empty if (!$id || !is_numeric($id)) { return i18n::s('No item has the provided id.'); } // server offset $server_offset = 0; if (isset($context['gmt_offset'])) { $server_offset = intval($context['gmt_offset']); } // surfer offset $surfer_offset = Surfer::get_gmt_offset(); // no publication time is provided if (!isset($publication) || !$publication) { $publication_stamp = 0; } elseif (preg_match('/GMT$/', $publication) && strlen($publication) == 19) { // YYMMDD-HH:MM:SS GMT -> HH, MM, SS, MM, DD, YY $publication_stamp = gmmktime(intval(substr($publication, 7, 2)), intval(substr($publication, 10, 2)), intval(substr($publication, 13, 2)), intval(substr($publication, 2, 2)), intval(substr($publication, 4, 2)), intval(substr($publication, 0, 2))); // time()-like stamp } elseif (intval($publication) > 1000000000) { // adjust to UTC time zone $publication_stamp = intval($publication) + $context['gmt_offset'] * 3600; // YYYY-MM-DD HH:MM:SS, or a string that can be readed } elseif (($publication_stamp = SQL::strtotime($publication)) != -1) { } else { return sprintf(i18n::s('"%s" is not a valid date'), $publication); } // no expiry date if (!isset($expiry) || !$expiry) { $expiry_stamp = 0; } elseif (preg_match('/GMT$/', $expiry) && strlen($expiry) == 19) { // YYMMDD-HH:MM:SS GMT -> HH, MM, SS, MM, DD, YY $expiry_stamp = gmmktime(substr($expiry, 7, 2), substr($expiry, 10, 2), substr($expiry, 13, 2), substr($expiry, 2, 2), substr($expiry, 4, 2), substr($expiry, 0, 2)); // time()-like stamp } elseif (intval($expiry) > 1000000000) { // adjust to server time zone $expiry_stamp = intval($expiry) + $context['gmt_offset'] * 3600; // YYYY-MM-DD HH:MM:SS, or a string that can be readed } elseif (($expiry_stamp = SQL::strtotime($expiry)) != -1) { } else { return sprintf(i18n::s('"%s" is not a valid date'), $expiry); } // review date $review_stamp = 0; if (!$publication_stamp && !$expiry_stamp) { $review_stamp = time(); } // shape the query $query = array(); if ($publication_stamp > 0) { $query[] = "publish_name='" . SQL::escape(isset($publisher['name']) ? $publisher['name'] : Surfer::get_name()) . "'," . "publish_id=" . SQL::escape(isset($publisher['id']) ? $publisher['id'] : Surfer::get_id()) . "," . "publish_address='" . SQL::escape(isset($publisher['address']) ? $publisher['address'] : Surfer::get_email_address()) . "'," . "publish_date='" . gmstrftime('%Y-%m-%d %H:%M:%S', $publication_stamp) . "'," . "edit_name='" . SQL::escape(isset($publisher['name']) ? $publisher['name'] : Surfer::get_name()) . "'," . "edit_id=" . SQL::escape(isset($publisher['id']) ? $publisher['id'] : Surfer::get_id()) . "," . "edit_address='" . SQL::escape(isset($publisher['address']) ? $publisher['address'] : Surfer::get_email_address()) . "'," . "edit_action='article:publish'," . "edit_date='" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'"; } if ($expiry_stamp > 0) { $query[] = "expiry_date='" . gmstrftime('%Y-%m-%d %H:%M:%S', $expiry_stamp) . "'"; } if ($review_stamp > 0) { $query[] = "review_date='" . gmstrftime('%Y-%m-%d %H:%M:%S', $review_stamp) . "'"; } // update an existing record $query = "UPDATE " . SQL::table_name('articles') . " SET " . implode(',', $query) . " WHERE id = " . SQL::escape($id); if (SQL::query($query) === FALSE) { return NULL; } // remember the publication in weekly and monthly categories if ($publication_stamp > 0) { Categories::remember('article:' . $id, gmstrftime('%Y-%m-%d %H:%M:%S', $publication_stamp)); } // end of job return NULL; }
/** * remember the last action for this category * * @param string the description of the last action * @param string the id of the item related to this update * @param boolean TRUE to not change the edit date of this anchor, default is FALSE * * @see shared/anchor.php */ function touch($action, $origin = NULL, $silently = FALSE) { global $context; // don't go further on import if (preg_match('/import$/i', $action)) { return; } // no category bound if (!isset($this->item['id'])) { return; } // sanity check if (!$origin) { logger::remember('categories/category.php: unexpected NULL origin at touch()'); return; } // components of the query $query = array(); // append a reference to a new image to the description if ($action == 'image:create') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { // the overlay may prevent embedding if (is_object($this->overlay) && !$this->overlay->should_embed_files()) { } else { // list has already started if (preg_match('/\\[image=[^\\]]+?\\]\\s*$/', $this->item['description'])) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } else { $query[] = "description = '" . SQL::escape($this->item['description'] . "\n\n" . '[image=' . $origin . ']') . "'"; } } } // also use it as thumnail if none has been defined yet if (!isset($this->item['thumbnail_url']) || !trim($this->item['thumbnail_url'])) { include_once $context['path_to_root'] . 'images/images.php'; if (($image = Images::get($origin)) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } // refresh stamp only if image update occurs within 6 hours after last edition if (SQL::strtotime($this->item['edit_date']) + 6 * 60 * 60 < time()) { $silently = TRUE; } // suppress a reference to an image that has been deleted } elseif ($action == 'image:delete') { // suppress reference in main description field $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'image', $origin)) . "'"; // suppress references as icon and thumbnail as well include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } if ($url = Images::get_thumbnail_href($image)) { if ($this->item['icon_url'] == $url) { $query[] = "icon_url = ''"; } if ($this->item['thumbnail_url'] == $url) { $query[] = "thumbnail_url = ''"; } } } // set an existing image as the category icon } elseif ($action == 'image:set_as_icon') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_icon_href($image)) { $query[] = "icon_url = '" . SQL::escape($url) . "'"; } // also use it as thumnail if none has been defined yet if (!(isset($this->item['thumbnail_url']) && trim($this->item['thumbnail_url'])) && ($url = Images::get_thumbnail_href($image))) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } $silently = TRUE; // set an existing image as the category thumbnail } elseif ($action == 'image:set_as_thumbnail') { include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_thumbnail_href($image)) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } $silently = TRUE; // append a new image, and set it as the article thumbnail } elseif ($action == 'image:set_as_both') { if (!Codes::check_embedded($this->item['description'], 'image', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [image=' . $origin . ']') . "'"; } include_once $context['path_to_root'] . 'images/images.php'; if ($image = Images::get($origin)) { if ($url = Images::get_thumbnail_href($image)) { $query[] = "thumbnail_url = '" . SQL::escape($url) . "'"; } } elseif ($origin) { $query[] = "thumbnail_url = '" . SQL::escape($origin) . "'"; } // do not remember minor changes $silently = TRUE; // add a reference to a new table in the category description } elseif ($action == 'table:create') { if (!Codes::check_embedded($this->item['description'], 'table', $origin)) { $query[] = "description = '" . SQL::escape($this->item['description'] . ' [table=' . $origin . ']') . "'"; } // suppress a reference to a table that has been deleted } elseif ($action == 'table:delete') { $query[] = "description = '" . SQL::escape(Codes::delete_embedded($this->item['description'], 'table', $origin)) . "'"; } // stamp the update if (!$silently) { $query[] = "edit_name='" . Surfer::get_name() . "'," . "edit_id=" . Surfer::get_id() . "," . "edit_address='" . Surfer::get_email_address() . "'," . "edit_action='{$action}'," . "edit_date='" . strftime('%Y-%m-%d %H:%M:%S') . "'"; } // ensure we have a valid update query if (!@count($query)) { return; } // update the anchor category $query = "UPDATE " . SQL::table_name('categories') . " SET " . implode(', ', $query) . " WHERE id = " . SQL::escape($this->item['id']); if (SQL::query($query) === FALSE) { return; } // always clear the cache, even on no update Categories::clear($this->item); // get the parent if (!$this->anchor) { $this->anchor = Anchors::get($this->item['anchor']); } // propagate the touch upwards silently -- we only want to purge the cache if (is_object($this->anchor)) { $this->anchor->touch('category:update', $this->item['id'], TRUE); } }
/** * get event description in ICS format * * @param string either 'PUBLISH', 'REQUEST', 'CANCEL', ... * @return string content of the ICS * * @see articles/invite.php * @see overlays/events/fetch_ics.php */ function get_ics($method = 'PUBLISH') { global $context; // begin calendar $text = 'BEGIN:VCALENDAR' . CRLF . 'VERSION:2.0' . CRLF . 'PRODID:' . $context['host_name'] . CRLF; // method $text .= 'METHOD:' . $method . CRLF; // begin event $text .= 'BEGIN:VEVENT' . CRLF; // date of creation if ($value = $this->anchor->get_value('create_date')) { $text .= 'DTSTAMP:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($value)) . CRLF; } // date of last modification --also required by Outlook if ($value = $this->anchor->get_value('edit_date')) { $text .= 'LAST-MODIFICATION:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($value)) . CRLF; } // the event spans limited time --duration is expressed in minutes if (isset($this->attributes['duration']) && $this->attributes['duration'] && $this->attributes['duration'] < 1440) { $text .= 'DTSTART:' . str_replace(array('-', ' ', ':'), array('', 'T', ''), $this->attributes['date_stamp']) . 'Z' . CRLF; $text .= 'DTEND:' . gmdate('Ymd\\THis\\Z', SQL::strtotime($this->attributes['date_stamp']) + $this->attributes['duration'] * 60) . CRLF; // a full-day event } elseif (isset($this->attributes['date_stamp'])) { $text .= 'DTSTART;VALUE=DATE:' . date('Ymd', SQL::strtotime($this->attributes['date_stamp'])) . CRLF; $text .= 'DTEND;VALUE=DATE:' . date('Ymd', SQL::strtotime($this->attributes['date_stamp']) + 86400) . CRLF; } // url to view the date $text .= 'URL:' . $context['url_to_home'] . $context['url_to_root'] . $this->anchor->get_url() . CRLF; // location is the yacs server $text .= 'LOCATION:' . $context['host_name'] . CRLF; // build a valid title if ($value = $this->anchor->get_title()) { $text .= 'SUMMARY:' . Codes::beautify_title($value) . CRLF; } // description is partially automated $text .= 'DESCRIPTION:'; if ($value = $this->anchor->get_title()) { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Topic'), str_replace(array("\n", "\r"), ' ', Codes::beautify_title($value))) . '\\n'; } // dates if (isset($this->attributes['date_stamp']) && $this->attributes['date_stamp']) { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Date'), Skin::build_date($this->attributes['date_stamp'], 'standalone')) . '\\n'; } if (isset($this->attributes['duration']) && $this->attributes['duration'] && $this->attributes['duration'] < 1440) { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Duration'), $this->attributes['duration'] . ' ' . i18n::s('minutes')) . '\\n'; } // build a link to the chairman page, if any if (isset($this->attributes['chairman']) && ($user = Users::get($this->attributes['chairman']))) { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Chairman'), $user['full_name']) . '\\n'; } elseif (($owner = $this->anchor->get_value('owner_id')) && ($user = Users::get($owner))) { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Chairman'), $user['full_name']) . '\\n'; } // location if ($method != 'CANCEL') { $text .= sprintf(i18n::s('%s: %s'), i18n::s('Location'), $context['url_to_home'] . $context['url_to_root'] . $this->anchor->get_url()) . '\\n'; } // meeting has been cancelled if ($method == 'CANCEL') { $text .= '\\n\\n' . i18n::c('Meeting has been cancelled.'); // regular meeting } else { // copy content of the introduction field, if any if ($value = $this->anchor->get_value('introduction')) { $text .= '\\n' . strip_tags(str_replace(array('</', '/>', "\n", "\r", ','), array('\\n</', '/>\\n', '\\n', ' ', '\\,'), Codes::beautify($value))); } // copy the induction message, if any if (isset($this->attributes['induction_message'])) { $text .= '\\n' . strip_tags(str_replace(array('</', '/>', "\n", "\r", ','), array('\\n</', '/>\\n', '\\n', ' ', '\\,'), Codes::render($this->attributes['induction_message']))); } } // end of the description field $text .= CRLF; // may be used for updates or for cancellation --required by Outlook 2003 $text .= 'UID:' . crc32($this->anchor->get_reference() . ':' . $context['url_to_root']) . '@' . $context['host_name'] . CRLF; // maybe this one has been cancelled if ($method == 'CANCEL') { $text .= 'STATUS:CANCELLED' . CRLF; } // sequence is incremented on each revision include_once $context['path_to_root'] . 'versions/versions.php'; $versions = Versions::count_for_anchor($this->anchor->get_reference()); $text .= 'SEQUENCE:' . $versions . CRLF; // more attributes $text .= 'PRIORITY:5' . CRLF . 'TRANSP:OPAQUE' . CRLF . 'CLASS:PUBLIC' . CRLF; // alarm to remind the meeting if ($method != 'CANCEL') { $text .= 'BEGIN:VALARM' . CRLF . 'TRIGGER:-PT15M' . CRLF . 'DESCRIPTION:Reminder' . CRLF . 'ACTION:DISPLAY' . CRLF . 'END:VALARM' . CRLF; } // close event $text .= 'END:VEVENT' . CRLF; // close calendar $text .= 'END:VCALENDAR' . CRLF; // done! return $text; }
/** * list articles * * @param resource the SQL result * @return array * * @see layouts/layout.php **/ function layout($result) { global $context; // we return an array of ($url => $attributes) $items = array(); // empty list if (!SQL::count($result)) { return $items; } // process all items in the list include_once $context['path_to_root'] . 'articles/article.php'; include_once $context['path_to_root'] . 'comments/comments.php'; include_once $context['path_to_root'] . 'locations/locations.php'; while ($item = SQL::fetch($result)) { // get the related overlay, if any $overlay = Overlay::load($item, 'article:' . $item['id']); // get the anchor $anchor = Anchors::get($item['anchor']); // provide an absolute link $url = Articles::get_permalink($item); // build a title if (is_object($overlay)) { $title = Codes::beautify_title($overlay->get_text('title', $item)); } else { $title = Codes::beautify_title($item['title']); } // time of last update $time = SQL::strtotime($item['edit_date']); // the section $section = ''; if ($item['anchor'] && ($anchor = Anchors::get($item['anchor']))) { $section = ucfirst(trim(strip_tags(Codes::beautify_title($anchor->get_title())))); } // the icon to use $icon = ''; if ($item['thumbnail_url']) { $icon = $item['thumbnail_url']; } elseif ($item['anchor'] && ($anchor = Anchors::get($item['anchor'])) && is_callable($anchor, 'get_bullet_url')) { $icon = $anchor->get_bullet_url(); } if ($icon) { $icon = $context['url_to_home'] . $context['url_to_root'] . $icon; } // the author(s) is an e-mail address, according to rss 2.0 spec $author = ''; if (isset($item['create_address'])) { $author .= $item['create_address']; } if (isset($item['create_name']) && trim($item['create_name'])) { $author .= ' (' . $item['create_name'] . ')'; } if (isset($item['edit_address']) && trim($item['edit_address']) && $item['create_address'] != $item['edit_address']) { if ($author) { $author .= ', '; } $author .= $item['edit_address']; if (isset($item['edit_name']) && trim($item['edit_name'])) { $author .= ' (' . $item['edit_name'] . ')'; } } // some introductory text for this article $article = new Article(); $article->load_by_content($item); $introduction = $article->get_teaser('teaser'); // warns on restricted access if (isset($item['active']) && $item['active'] != 'Y') { $introduction = '[' . i18n::c('Restricted to members') . '] ' . $introduction; } // fix references $introduction = preg_replace('/"\\//', '"' . $context['url_to_home'] . '/', $introduction); // the article content $description = ''; // other rss fields $extensions = array(); // the geolocation for this page, if any if ($location = Locations::locate_anchor('article:' . $item['id'])) { $extensions[] = '<georss:point>' . str_replace(',', ' ', $location) . '</georss:point>'; } // url for comments if (is_object($anchor)) { $extensions[] = '<comments>' . encode_link($context['url_to_home'] . $context['url_to_root'] . $anchor->get_url('comments')) . '</comments>'; } // count comments $comment_count = Comments::count_for_anchor('article:' . $item['id']); $extensions[] = '<slash:comments>' . $comment_count . "</slash:comments>"; // the comment post url $extensions[] = '<wfw:comment>' . encode_link($context['url_to_home'] . $context['url_to_root'] . Comments::get_url('article:' . $item['id'], 'service.comment')) . "</wfw:comment>"; // the comment Rss url $extensions[] = '<wfw:commentRss>' . encode_link($context['url_to_home'] . $context['url_to_root'] . Comments::get_url('article:' . $item['id'], 'feed')) . "</wfw:commentRss>"; // the trackback url $extensions[] = '<trackback:ping>' . encode_link($context['url_to_home'] . $context['url_to_root'] . 'links/trackback.php?anchor=' . urlencode('article:' . $item['id'])) . "</trackback:ping>"; // no trackback:about; // list all components for this item $items[$url] = array($time, $title, $author, $section, $icon, $introduction, $description, $extensions); } // end of processing SQL::free($result); return $items; }
/** * list comments * * @param resource the SQL result * @return string the rendered text * * @see layouts/layout.php **/ function layout($result) { global $context; // empty list if (!SQL::count($result)) { $output = array(); return $output; } // we return an array of ($url => $attributes) $items = array(); // process all items in the list include_once $context['path_to_root'] . 'comments/comments.php'; while ($item = SQL::fetch($result)) { // get the anchor for this comment $anchor = NULL; if (isset($item['anchor']) && $item['anchor']) { $anchor = Anchors::get($item['anchor']); } // url to read the full comment $url = $context['url_to_home'] . $context['url_to_root'] . Comments::get_url($item['id']); // time of last update $time = SQL::strtotime($item['edit_date']); // the title as the label $label = ''; if ($item['create_name']) { $label .= ucfirst($item['create_name']); } if (is_object($anchor)) { $label .= ' ' . sprintf(i18n::s('on %s'), $anchor->get_title()); } // the section $section = ''; if (is_object($anchor)) { $section = ucfirst($anchor->get_title()); } // the icon to use $icon = ''; if (isset($item['thumbnail_url']) && $item['thumbnail_url']) { $icon = $item['thumbnail_url']; } elseif (is_object($anchor)) { $icon = $anchor->get_thumbnail_url(); } if ($icon) { $icon = $context['url_to_home'] . $icon; } // the author(s) is an e-mail address, according to rss 2.0 spec $author = $item['create_address'] . ' (' . $item['create_name'] . ')'; if ($item['create_address'] != $item['edit_address']) { if ($author) { $author .= ', '; } $author .= $item['edit_address'] . ' (' . $item['edit_name'] . ')'; } // the comment content $description = Codes::beautify($item['description']); // cap the number of words // $description = Skin::cap($description, 300); // fix image references $description = preg_replace('#"/([^">]+?)"#', '"' . $context['url_to_home'] . '/$1"', $description); $introduction = $description; // other rss fields $extensions = array(); // list all components for this item $items[$url] = array($time, $label, $author, $section, $icon, $introduction, $description, $extensions); } // end of processing SQL::free($result); return $items; }