3 /***************************************************************************
5 * Copyright (c) by Andreas Unterkircher, unki@netshadow.at
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 ***************************************************************************/
24 require_once "phpfspot_cfg.php";
25 require_once "phpfspot_db.php";
36 private $runtime_error = false;
42 * this function will be called on class construct
43 * and will check requirements, loads configuration,
44 * open databases and start the user session
46 public function __construct()
48 $this->cfg = new PHPFSPOT_CFG;
50 /* verify config settings */
51 if($this->check_config_options()) {
55 /* set application name and version information */
56 $this->cfg->product = "phpfspot";
57 $this->cfg->version = "1.4";
59 $this->sort_orders= array(
60 'date_asc' => 'Date ↑',
61 'date_desc' => 'Date ↓',
62 'name_asc' => 'Name ↑',
63 'name_desc' => 'Name ↓',
64 'tags_asc' => 'Tags ↑',
65 'tags_desc' => 'Tags ↓',
68 /* Check necessary requirements */
69 if(!$this->checkRequirements()) {
73 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
74 if(!is_writeable($this->cfg->fspot_db)) {
75 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
79 $this->dbver = $this->getFspotDBVersion();
81 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
82 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
86 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
87 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
91 if(!is_writeable($this->cfg->thumb_path)) {
92 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
96 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
97 if(!is_writeable($this->cfg->phpfspot_db)) {
98 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
102 $this->check_config_table();
104 /* include Smarty template engine */
105 if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
108 require $this->cfg->smarty_path .'/libs/Smarty.class.php';
109 /* overload Smarty class if our own template handler */
110 require_once "phpfspot_tmpl.php";
111 $this->tmpl = new PHPFSPOT_TMPL($this);
113 /* check if all necessary indices exist */
114 $this->checkDbIndices();
116 /* if session is not yet started, do it now */
117 if(session_id() == "")
120 if(!isset($_SESSION['tag_condition']))
121 $_SESSION['tag_condition'] = 'or';
123 if(!isset($_SESSION['sort_order']))
124 $_SESSION['sort_order'] = 'date_desc';
126 if(!isset($_SESSION['searchfor_tag']))
127 $_SESSION['searchfor_tag'] = '';
129 // if begin_with is still set but thumbs_per_page is now 0, unset it
130 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
131 unset($_SESSION['begin_with']);
135 public function __destruct()
141 * show - generate html output
143 * this function can be called after the constructor has
144 * prepared everyhing. it will load the index.tpl smarty
145 * template. if necessary it will registere pre-selects
146 * (photo index, photo, tag search, date search) into
149 public function show()
151 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
152 $this->tmpl->assign('page_title', $this->cfg->page_title);
153 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
154 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
156 if(isset($_GET['mode'])) {
158 $_SESSION['start_action'] = $_GET['mode'];
160 switch($_GET['mode']) {
162 if(isset($_GET['tags'])) {
163 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
165 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
166 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
168 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
169 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
173 if(isset($_GET['tags'])) {
174 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
175 $_SESSION['start_action'] = 'showp';
177 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
178 $_SESSION['current_photo'] = $_GET['id'];
179 $_SESSION['start_action'] = 'showp';
181 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
182 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
184 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
185 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
189 $this->tmpl->show("export.tpl");
193 $this->tmpl->show("slideshow.tpl");
197 if(isset($_GET['tags'])) {
198 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
200 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
201 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
203 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
204 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
212 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
213 $this->tmpl->assign('date_search_enabled', true);
215 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
216 $this->tmpl->assign('from_date', $this->get_calendar('from'));
217 $this->tmpl->assign('to_date', $this->get_calendar('to'));
218 $this->tmpl->assign('content_page', 'welcome.tpl');
219 $this->tmpl->show("index.tpl");
224 * get_tags - grab all tags of f-spot's database
226 * this function will get all available tags from
227 * the f-spot database and store them within two
228 * arrays within this class for later usage. in
229 * fact, if the user requests (hide_tags) it will
230 * opt-out some of them.
232 * this function is getting called once by show()
234 private function get_tags()
236 $this->avail_tags = Array();
239 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
242 DISTINCT t1.id as id, t1.name as name
245 INNER JOIN photo_tags
246 pt2 ON pt1.photo_id=pt2.photo_id
252 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
254 t1.sort_priority ASC";
256 $result = $this->db->db_query($query_str);
260 $result = $this->db->db_query("
263 ORDER BY sort_priority ASC
267 while($row = $this->db->db_fetch_object($result)) {
269 $tag_id = $row['id'];
270 $tag_name = $row['name'];
272 /* if the user has specified to ignore this tag in phpfspot's
273 configuration, ignore it here so it does not get added to
276 if(in_array($row['name'], $this->cfg->hide_tags))
279 /* if you include the following if-clause and the user has specified
280 to only show certain tags which are specified in phpfspot's
281 configuration, ignore all others so they will not be added to the
283 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
284 !in_array($row['name'], $this->cfg->show_tags))
288 $this->tags[$tag_id] = $tag_name;
289 $this->avail_tags[$count] = $tag_id;
297 * extract all photo details
299 * retrieve all available details from f-spot's
300 * database and return them as object
302 public function get_photo_details($idx)
304 if($this->dbver < 9) {
306 SELECT p.id, p.name, p.time, p.directory_path, p.description
312 SELECT p.id, p.uri, p.time, p.description
317 /* if show_tags is set, only return details for photos which
318 are specified to be shown
320 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
322 INNER JOIN photo_tags pt
326 WHERE p.id='". $idx ."'
327 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
331 WHERE p.id='". $idx ."'
335 if($result = $this->db->db_query($query_str)) {
337 $row = $this->db->db_fetch_object($result);
339 if($this->dbver < 9) {
340 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
349 } // get_photo_details
352 * returns aligned photo names
354 * this function returns aligned (length) names for
355 * an specific photo. If the length of the name exceeds
356 * $limit the name will be shrinked (...)
358 public function getPhotoName($idx, $limit = 0)
360 if($details = $this->get_photo_details($idx)) {
361 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
362 $name = $this->shrink_text($long_name, $limit);
372 * shrink text according provided limit
374 * If the length of the name exceeds $limit the
375 * text will be shortend and some content in between
376 * will be replaced with "..."
378 private function shrink_text($text, $limit)
380 if($limit != 0 && strlen($text) > $limit) {
381 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
389 * translate f-spoth photo path
391 * as the full-qualified path recorded in the f-spot database
392 * is usally not the same as on the webserver, this function
393 * will replace the path with that one specified in the cfg
395 public function translate_path($path, $width = 0)
397 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
402 * control HTML ouput for a single photo
404 * this function provides all the necessary information
405 * for the single photo template.
407 public function showPhoto($photo)
409 /* get all photos from the current photo selection */
410 $all_photos = $this->getPhotoSelection();
411 $count = count($all_photos);
413 for($i = 0; $i < $count; $i++) {
415 // $get_next will be set, when the photo which has to
416 // be displayed has been found - this means that the
417 // next available is in fact the NEXT image (for the
419 if(isset($get_next)) {
420 $next_img = $all_photos[$i];
424 /* the next photo is our NEXT photo */
425 if($all_photos[$i] == $photo) {
429 $previous_img = $all_photos[$i];
432 if($photo == $all_photos[$i]) {
437 $details = $this->get_photo_details($photo);
444 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
445 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
447 if(!file_exists($orig_path)) {
448 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
452 if(!is_readable($orig_path)) {
453 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
457 /* If the thumbnail doesn't exist yet, try to create it */
458 if(!file_exists($thumb_path)) {
459 $this->gen_thumb($photo, true);
460 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
463 /* get EXIF information if JPEG */
464 if($details['mime'] == "image/jpeg") {
465 $meta = $this->get_meta_informations($orig_path);
468 /* If EXIF data are available, use them */
469 if(isset($meta['ExifImageWidth'])) {
470 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
472 $info = getimagesize($orig_path);
473 $meta_res = $info[0] ."x". $info[1];
476 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
477 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
478 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
480 $extern_link = "index.php?mode=showp&id=". $photo;
481 $current_tags = $this->getCurrentTags();
482 if($current_tags != "") {
483 $extern_link.= "&tags=". $current_tags;
485 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
486 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
489 $this->tmpl->assign('extern_link', $extern_link);
491 if(!file_exists($thumb_path)) {
492 $this->_error("Can't open file ". $thumb_path ."\n");
496 $info = getimagesize($thumb_path);
498 $this->tmpl->assign('description', $details['description']);
499 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
501 $this->tmpl->assign('width', $info[0]);
502 $this->tmpl->assign('height', $info[1]);
503 $this->tmpl->assign('ExifMadeOn', $meta_date);
504 $this->tmpl->assign('ExifMadeWith', $meta_make);
505 $this->tmpl->assign('ExifOrigResolution', $meta_res);
506 $this->tmpl->assign('ExifFileSize', $meta_size);
508 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
509 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
510 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
512 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
513 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
514 $this->tmpl->assign('current_img', $photo);
517 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
518 $this->tmpl->assign('prev_img', $previous_img);
522 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
523 $this->tmpl->assign('next_img', $next_img);
525 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
526 $this->tmpl->assign('photo_number', $i);
527 $this->tmpl->assign('photo_count', count($all_photos));
529 $this->tmpl->show("single_photo.tpl");
534 * all available tags and tag cloud
536 * this function outputs all available tags (time ordered)
537 * and in addition output them as tag cloud (tags which have
538 * many photos will appears more then others)
540 public function getAvailableTags()
542 /* retrive tags from database */
547 $result = $this->db->db_query("
548 SELECT tag_id as id, count(tag_id) as quantity
558 while($row = $this->db->db_fetch_object($result)) {
559 $tags[$row['id']] = $row['quantity'];
562 // change these font sizes if you will
563 $max_size = 125; // max font size in %
564 $min_size = 75; // min font size in %
566 // get the largest and smallest array values
567 $max_qty = max(array_values($tags));
568 $min_qty = min(array_values($tags));
570 // find the range of values
571 $spread = $max_qty - $min_qty;
572 if (0 == $spread) { // we don't want to divide by zero
576 // determine the font-size increment
577 // this is the increase per tag quantity (times used)
578 $step = ($max_size - $min_size)/($spread);
580 // loop through our tag array
581 foreach ($tags as $key => $value) {
583 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
586 // calculate CSS font-size
587 // find the $value in excess of $min_qty
588 // multiply by the font-size increment ($size)
589 // and add the $min_size set above
590 $size = $min_size + (($value - $min_qty) * $step);
591 // uncomment if you want sizes in whole %:
594 if(isset($this->tags[$key])) {
595 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
600 $output = substr($output, 0, strlen($output)-2);
603 } // getAvailableTags()
606 * output all selected tags
608 * this function output all tags which have been selected
609 * by the user. the selected tags are stored in the
610 * session-variable $_SESSION['selected_tags']
612 public function getSelectedTags()
614 /* retrive tags from database */
619 foreach($this->avail_tags as $tag)
621 // return all selected tags
622 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
623 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
628 $output = substr($output, 0, strlen($output)-2);
632 return "no tags selected";
635 } // getSelectedTags()
638 * add tag to users session variable
640 * this function will add the specified to users current
641 * tag selection. if a date search has been made before
642 * it will be now cleared
644 public function addTag($tag)
646 if(!isset($_SESSION['selected_tags']))
647 $_SESSION['selected_tags'] = Array();
649 if(isset($_SESSION['searchfor_tag']))
650 unset($_SESSION['searchfor_tag']);
652 if(!in_array($tag, $_SESSION['selected_tags']))
653 array_push($_SESSION['selected_tags'], $tag);
661 * remove tag to users session variable
663 * this function removes the specified tag from
664 * users current tag selection
666 public function delTag($tag)
668 if(isset($_SESSION['searchfor_tag']))
669 unset($_SESSION['searchfor_tag']);
671 if(isset($_SESSION['selected_tags'])) {
672 $key = array_search($tag, $_SESSION['selected_tags']);
673 unset($_SESSION['selected_tags'][$key]);
674 sort($_SESSION['selected_tags']);
682 * reset tag selection
684 * if there is any tag selection, it will be
687 public function resetTags()
689 if(isset($_SESSION['selected_tags']))
690 unset($_SESSION['selected_tags']);
695 * returns the value for the autocomplet tag-search
697 public function get_xml_tag_list()
699 if(!isset($_GET['search']) || !is_string($_GET['search']))
700 $_GET['search'] = '';
705 /* retrive tags from database */
708 $matched_tags = Array();
710 header("Content-Type: text/xml");
712 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
713 $string.= "<results>\n";
715 foreach($this->avail_tags as $tag)
717 if(!empty($_GET['search']) &&
718 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
719 count($matched_tags) < $length) {
721 $count = $this->get_num_photos($tag);
724 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
727 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
733 /* if we have collected enough items, break out */
734 if(count($matched_tags) >= $length)
738 $string.= "</results>\n";
742 } // get_xml_tag_list()
748 * if a specific photo was requested (external link)
749 * unset the session variable now
751 public function resetPhotoView()
753 if(isset($_SESSION['current_photo']))
754 unset($_SESSION['current_photo']);
756 } // resetPhotoView();
761 * if any tag search has taken place, reset it now
763 public function resetTagSearch()
765 if(isset($_SESSION['searchfor_tag']))
766 unset($_SESSION['searchfor_tag']);
768 } // resetTagSearch()
773 * if any name search has taken place, reset it now
775 public function resetNameSearch()
777 if(isset($_SESSION['searchfor_name']))
778 unset($_SESSION['searchfor_name']);
780 } // resetNameSearch()
785 * if any date search has taken place, reset
788 public function resetDateSearch()
790 if(isset($_SESSION['from_date']))
791 unset($_SESSION['from_date']);
792 if(isset($_SESSION['to_date']))
793 unset($_SESSION['to_date']);
795 } // resetDateSearch();
798 * return all photo according selection
800 * this function returns all photos based on
801 * the tag-selection, tag- or date-search.
802 * the tag-search also has to take care of AND
803 * and OR conjunctions
805 public function getPhotoSelection()
807 $matched_photos = Array();
808 $additional_where_cond = "";
810 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
811 $from_date = $_SESSION['from_date'];
812 $to_date = $_SESSION['to_date'];
813 $additional_where_cond.= "
814 p.time>='". $from_date ."'
816 p.time<='". $to_date ."'
820 if(isset($_SESSION['searchfor_name'])) {
821 if($this->dbver < 9) {
822 $additional_where_cond.= "
824 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
826 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
831 $additional_where_cond.= "
833 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
835 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
841 if(isset($_SESSION['sort_order'])) {
842 $order_str = $this->get_sort_order();
845 /* return a search result */
846 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
848 SELECT DISTINCT pt1.photo_id
850 INNER JOIN photo_tags pt2
851 ON pt1.photo_id=pt2.photo_id
858 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
860 if(isset($additional_where_cond) && !empty($additional_where_cond))
861 $query_str.= "AND ". $additional_where_cond ." ";
863 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
864 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
867 if(isset($order_str))
868 $query_str.= $order_str;
870 $result = $this->db->db_query($query_str);
871 while($row = $this->db->db_fetch_object($result)) {
872 array_push($matched_photos, $row['photo_id']);
874 return $matched_photos;
877 /* return according the selected tags */
878 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
880 foreach($_SESSION['selected_tags'] as $tag)
881 $selected.= $tag .",";
882 $selected = substr($selected, 0, strlen($selected)-1);
884 /* photo has to match at least on of the selected tags */
885 if($_SESSION['tag_condition'] == 'or') {
887 SELECT DISTINCT pt1.photo_id
889 INNER JOIN photo_tags pt2
890 ON pt1.photo_id=pt2.photo_id
895 WHERE pt1.tag_id IN (". $selected .")
897 if(isset($additional_where_cond) && !empty($additional_where_cond))
898 $query_str.= "AND ". $additional_where_cond ." ";
900 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
901 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
904 if(isset($order_str))
905 $query_str.= $order_str;
907 /* photo has to match all selected tags */
908 elseif($_SESSION['tag_condition'] == 'and') {
910 if(count($_SESSION['selected_tags']) >= 32) {
911 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
912 print "evaluate your tag selection. Please remove some tags from your selection.\n";
916 /* Join together a table looking like
918 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
920 so the query can quickly return all images matching the
921 selected tags in an AND condition
926 SELECT DISTINCT pt1.photo_id
930 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
937 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
939 INNER JOIN photo_tags pt". ($i+2) ."
940 ON pt1.photo_id=pt". ($i+2) .".photo_id
947 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
948 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
950 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
953 if(isset($additional_where_cond) && !empty($additional_where_cond))
954 $query_str.= "AND ". $additional_where_cond;
956 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
957 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
960 if(isset($order_str))
961 $query_str.= $order_str;
965 $result = $this->db->db_query($query_str);
966 while($row = $this->db->db_fetch_object($result)) {
967 array_push($matched_photos, $row['photo_id']);
969 return $matched_photos;
972 /* return all available photos */
976 LEFT JOIN photo_tags pt
982 if(isset($additional_where_cond) && !empty($additional_where_cond))
983 $query_str.= "WHERE ". $additional_where_cond ." ";
985 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
986 if(isset($additional_where_cond) && !empty($additional_where_cond))
987 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
989 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
992 if(isset($order_str))
993 $query_str.= $order_str;
995 $result = $this->db->db_query($query_str);
996 while($row = $this->db->db_fetch_object($result)) {
997 array_push($matched_photos, $row['id']);
999 return $matched_photos;
1001 } // getPhotoSelection()
1004 * control HTML ouput for photo index
1006 * this function provides all the necessary information
1007 * for the photo index template.
1009 public function showPhotoIndex()
1011 $photos = $this->getPhotoSelection();
1013 $count = count($photos);
1015 /* if all thumbnails should be shown on one page */
1016 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1020 /* thumbnails should be splitted up in several pages */
1021 elseif($this->cfg->thumbs_per_page > 0) {
1023 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1027 $begin_with = $_SESSION['begin_with'];
1030 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1034 $images[$thumbs] = Array();
1035 $img_height[$thumbs] = Array();
1036 $img_width[$thumbs] = Array();
1037 $img_id[$thumbs] = Array();
1038 $img_name[$thumbs] = Array();
1039 $img_fullname[$thumbs] = Array();
1040 $img_title = Array();
1042 for($i = $begin_with; $i < $end_with; $i++) {
1044 if(isset($photos[$i])) {
1046 $images[$thumbs] = $photos[$i];
1047 $img_id[$thumbs] = $i;
1048 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1049 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1050 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1052 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1054 if(file_exists($thumb_path)) {
1055 $info = getimagesize($thumb_path);
1056 $img_width[$thumbs] = $info[0];
1057 $img_height[$thumbs] = $info[1];
1063 // +1 for for smarty's selection iteration
1066 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1067 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1069 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1070 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1071 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1074 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1075 $this->tmpl->assign('tag_result', 1);
1078 /* do we have to display the page selector ? */
1079 if($this->cfg->thumbs_per_page != 0) {
1083 /* calculate the page switchers */
1084 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1085 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1087 if($begin_with != 0)
1088 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1089 if($end_with < $count)
1090 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1092 $photo_per_page = $this->cfg->thumbs_per_page;
1093 $last_page = ceil($count / $photo_per_page);
1095 /* get the current selected page */
1096 if($begin_with == 0) {
1100 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1107 for($i = 1; $i <= $last_page; $i++) {
1109 if($current_page == $i)
1110 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1111 elseif($current_page-1 == $i || $current_page+1 == $i)
1112 $style = "style=\"font-size: 105%;\"";
1113 elseif(($current_page-5 >= $i) && ($i != 1) ||
1114 ($current_page+5 <= $i) && ($i != $last_page))
1115 $style = "style=\"font-size: 75%;\"";
1119 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1122 $select.= ">". $i ."</a> ";
1124 // until 9 pages we show the selector from 1-9
1125 if($last_page <= 9) {
1126 $page_select.= $select;
1129 if($i == 1 /* first page */ ||
1130 $i == $last_page /* last page */ ||
1131 $i == $current_page /* current page */ ||
1132 $i == ceil($last_page * 0.25) /* first quater */ ||
1133 $i == ceil($last_page * 0.5) /* half */ ||
1134 $i == ceil($last_page * 0.75) /* third quater */ ||
1135 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1136 (in_array($i, array($last_page, $last_page-1, $last_page-2, $last_page-3, $last_page-4, $last_page-5)) && $current_page >= $last_page-4) /* the last 6 */ ||
1137 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1138 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1140 $page_select.= $select;
1148 $page_select.= "......... ";
1153 /* only show the page selector if we have more then one page */
1155 $this->tmpl->assign('page_selector', $page_select);
1159 $current_tags = $this->getCurrentTags();
1160 $extern_link = "index.php?mode=showpi";
1161 $rss_link = "index.php?mode=rss";
1162 if($current_tags != "") {
1163 $extern_link.= "&tags=". $current_tags;
1164 $rss_link.= "&tags=". $current_tags;
1166 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1167 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1168 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1171 $export_link = "index.php?mode=export";
1172 $slideshow_link = "index.php?mode=slideshow";
1174 $this->tmpl->assign('extern_link', $extern_link);
1175 $this->tmpl->assign('slideshow_link', $slideshow_link);
1176 $this->tmpl->assign('export_link', $export_link);
1177 $this->tmpl->assign('rss_link', $rss_link);
1178 $this->tmpl->assign('count', $count);
1179 $this->tmpl->assign('width', $this->cfg->thumb_width);
1180 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1181 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1182 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1183 $this->tmpl->assign('images', $images);
1184 $this->tmpl->assign('img_width', $img_width);
1185 $this->tmpl->assign('img_height', $img_height);
1186 $this->tmpl->assign('img_id', $img_id);
1187 $this->tmpl->assign('img_name', $img_name);
1188 $this->tmpl->assign('img_fullname', $img_fullname);
1189 $this->tmpl->assign('img_title', $img_title);
1190 $this->tmpl->assign('thumbs', $thumbs);
1192 $this->tmpl->show("photo_index.tpl");
1194 /* if we are returning to photo index from an photo-view,
1195 scroll the window to the last shown photo-thumbnail.
1196 after this, unset the last_photo session variable.
1198 if(isset($_SESSION['last_photo'])) {
1199 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1200 unset($_SESSION['last_photo']);
1203 } // showPhotoIndex()
1206 * show credit template
1208 public function showCredits()
1210 $this->tmpl->assign('version', $this->cfg->version);
1211 $this->tmpl->assign('product', $this->cfg->product);
1212 $this->tmpl->assign('db_version', $this->dbver);
1213 $this->tmpl->show("credits.tpl");
1218 * create_thumbnails for the requested width
1220 * this function creates image thumbnails of $orig_image
1221 * stored as $thumb_image. It will check if the image is
1222 * in a supported format, if necessary rotate the image
1223 * (based on EXIF orientation meta headers) and re-sizing.
1225 public function create_thumbnail($orig_image, $thumb_image, $width)
1227 if(!file_exists($orig_image)) {
1231 $details = getimagesize($orig_image);
1233 /* check if original photo is a support image type */
1234 if(!$this->checkifImageSupported($details['mime']))
1237 switch($details['mime']) {
1241 $meta = $this->get_meta_informations($orig_image);
1247 switch($meta['Orientation']) {
1248 case 1: /* top, left */
1249 /* nothing to do */ break;
1250 case 2: /* top, right */
1251 $rotate = 0; $flip_hori = true; break;
1252 case 3: /* bottom, left */
1253 $rotate = 180; break;
1254 case 4: /* bottom, right */
1255 $flip_vert = true; break;
1256 case 5: /* left side, top */
1257 $rotate = 90; $flip_vert = true; break;
1258 case 6: /* right side, top */
1259 $rotate = 90; break;
1260 case 7: /* left side, bottom */
1261 $rotate = 270; $flip_vert = true; break;
1262 case 8: /* right side, bottom */
1263 $rotate = 270; break;
1266 $src_img = @imagecreatefromjpeg($orig_image);
1271 $src_img = @imagecreatefrompng($orig_image);
1277 print "Can't load image from ". $orig_image ."\n";
1281 /* grabs the height and width */
1282 $cur_width = imagesx($src_img);
1283 $cur_height = imagesy($src_img);
1285 // If requested width is more then the actual image width,
1286 // do not generate a thumbnail, instead safe the original
1287 // as thumbnail but with lower quality. But if the image
1288 // is to heigh too, then we still have to resize it.
1289 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1290 $result = imagejpeg($src_img, $thumb_image, 75);
1291 imagedestroy($src_img);
1295 // If the image will be rotate because EXIF orientation said so
1296 // 'virtually rotate' the image for further calculations
1297 if($rotate == 90 || $rotate == 270) {
1299 $cur_width = $cur_height;
1303 /* calculates aspect ratio */
1304 $aspect_ratio = $cur_height / $cur_width;
1307 if($aspect_ratio < 1) {
1309 $new_h = abs($new_w * $aspect_ratio);
1311 /* 'virtually' rotate the image and calculate it's ratio */
1312 $tmp_w = $cur_height;
1313 $tmp_h = $cur_width;
1314 /* now get the ratio from the 'rotated' image */
1315 $tmp_ratio = $tmp_h/$tmp_w;
1316 /* now calculate the new dimensions */
1318 $tmp_h = abs($tmp_w * $tmp_ratio);
1320 // now that we know, how high they photo should be, if it
1321 // gets rotated, use this high to scale the image
1323 $new_w = abs($new_h / $aspect_ratio);
1325 // If the image will be rotate because EXIF orientation said so
1326 // now 'virtually rotate' back the image for the image manipulation
1327 if($rotate == 90 || $rotate == 270) {
1334 /* creates new image of that size */
1335 $dst_img = imagecreatetruecolor($new_w, $new_h);
1337 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1339 /* copies resized portion of original image into new image */
1340 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1342 /* needs the image to be flipped horizontal? */
1344 $this->_debug("(FLIP)");
1345 $dst_img = $this->flipImage($dst_img, 'hori');
1347 /* needs the image to be flipped vertical? */
1349 $this->_debug("(FLIP)");
1350 $dst_img = $this->flipImage($dst_img, 'vert');
1354 $this->_debug("(ROTATE)");
1355 $dst_img = $this->rotateImage($dst_img, $rotate);
1358 /* write down new generated file */
1359 $result = imagejpeg($dst_img, $thumb_image, 75);
1361 /* free your mind */
1362 imagedestroy($dst_img);
1363 imagedestroy($src_img);
1365 if($result === false) {
1366 print "Can't write thumbnail ". $thumb_image ."\n";
1372 } // create_thumbnail()
1375 * return all exif meta data from the file
1377 public function get_meta_informations($file)
1379 return exif_read_data($file);
1381 } // get_meta_informations()
1384 * create phpfspot own sqlite database
1386 * this function creates phpfspots own sqlite database
1387 * if it does not exist yet. this own is used to store
1388 * some necessary informations (md5 sum's, ...).
1390 public function check_config_table()
1392 // if the config table doesn't exist yet, create it
1393 if(!$this->cfg_db->db_check_table_exists("images")) {
1394 $this->cfg_db->db_exec("
1395 CREATE TABLE images (
1396 img_idx int primary key,
1402 } // check_config_table
1405 * Generates a thumbnail from photo idx
1407 * This function will generate JPEG thumbnails from provided F-Spot photo
1410 * 1. Check if all thumbnail generations (width) are already in place and
1412 * 2. Check if the md5sum of the original file has changed
1413 * 3. Generate the thumbnails if needed
1415 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1419 $resolutions = Array(
1420 $this->cfg->thumb_width,
1421 $this->cfg->photo_width,
1422 $this->cfg->mini_width,
1425 /* get details from F-Spot's database */
1426 $details = $this->get_photo_details($idx);
1428 /* calculate file MD5 sum */
1429 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1431 if(!file_exists($full_path)) {
1432 $this->_error("File ". $full_path ." does not exist\n");
1436 if(!is_readable($full_path)) {
1437 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1441 $file_md5 = md5_file($full_path);
1443 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1447 foreach($resolutions as $resolution) {
1449 $generate_it = false;
1451 $thumb_sub_path = substr($file_md5, 0, 2);
1452 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1454 /* if thumbnail-subdirectory does not exist yet, create it */
1455 if(!file_exists(dirname($thumb_path))) {
1456 mkdir(dirname($thumb_path), 0755);
1459 /* if the thumbnail file doesn't exist, create it */
1460 if(!file_exists($thumb_path)) {
1461 $generate_it = true;
1463 /* if the file hasn't changed there is no need to regen the thumb */
1464 elseif($file_md5 != $this->getMD5($idx) || $force) {
1465 $generate_it = true;
1468 if($generate_it || $overwrite) {
1470 $this->_debug(" ". $resolution ."px");
1471 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1479 $this->_debug(" already exist");
1482 /* set the new/changed MD5 sum for the current photo */
1484 $this->setMD5($idx, $file_md5);
1487 $this->_debug("\n");
1492 * returns stored md5 sum for a specific photo
1494 * this function queries the phpfspot database for a
1495 * stored MD5 checksum of the specified photo
1497 public function getMD5($idx)
1499 $result = $this->cfg_db->db_query("
1502 WHERE img_idx='". $idx ."'
1508 $img = $this->cfg_db->db_fetch_object($result);
1509 return $img['img_md5'];
1514 * set MD5 sum for the specific photo
1516 private function setMD5($idx, $md5)
1518 $result = $this->cfg_db->db_exec("
1519 REPLACE INTO images (img_idx, img_md5)
1520 VALUES ('". $idx ."', '". $md5 ."')
1526 * store current tag condition
1528 * this function stores the current tag condition
1529 * (AND or OR) in the users session variables
1531 public function setTagCondition($mode)
1533 $_SESSION['tag_condition'] = $mode;
1537 } // setTagCondition()
1540 * invoke tag & date search
1542 * this function will return all matching tags and store
1543 * them in the session variable selected_tags. furthermore
1544 * it also handles the date search.
1545 * getPhotoSelection() will then only return the matching
1548 public function startSearch()
1550 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1551 $from = $_POST['from'];
1553 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1557 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1558 $searchfor_tag = $_POST['for_tag'];
1559 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1562 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1563 $searchfor_name = $_POST['for_name'];
1564 $_SESSION['searchfor_name'] = $_POST['for_name'];
1569 if(isset($from) && !empty($from))
1570 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1572 unset($_SESSION['from_date']);
1574 if(isset($to) && !empty($to))
1575 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1577 unset($_SESSION['to_date']);
1579 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1580 /* new search, reset the current selected tags */
1581 $_SESSION['selected_tags'] = Array();
1582 foreach($this->avail_tags as $tag) {
1583 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1584 array_push($_SESSION['selected_tags'], $tag);
1593 * updates sort order in session variable
1595 * this function is invoked by RPC and will sort the requested
1596 * sort order in the session variable.
1598 public function updateSortOrder($order)
1600 if(isset($this->sort_orders[$order])) {
1601 $_SESSION['sort_order'] = $order;
1605 return "unkown error";
1607 } // updateSortOrder()
1612 * this function rotates the image according the
1615 private function rotateImage($img, $degrees)
1617 if(function_exists("imagerotate")) {
1618 $img = imagerotate($img, $degrees, 0);
1620 function imagerotate($src_img, $angle)
1622 $src_x = imagesx($src_img);
1623 $src_y = imagesy($src_img);
1624 if ($angle == 180) {
1628 elseif ($src_x <= $src_y) {
1632 elseif ($src_x >= $src_y) {
1637 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1638 imagealphablending($rotate, false);
1643 for ($y = 0; $y < ($src_y); $y++) {
1644 for ($x = 0; $x < ($src_x); $x++) {
1645 $color = imagecolorat($src_img, $x, $y);
1646 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1652 for ($y = 0; $y < ($src_y); $y++) {
1653 for ($x = 0; $x < ($src_x); $x++) {
1654 $color = imagecolorat($src_img, $x, $y);
1655 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1661 for ($y = 0; $y < ($src_y); $y++) {
1662 for ($x = 0; $x < ($src_x); $x++) {
1663 $color = imagecolorat($src_img, $x, $y);
1664 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1678 $img = imagerotate($img, $degrees);
1687 * returns flipped image
1689 * this function will return an either horizontal or
1690 * vertical flipped truecolor image.
1692 private function flipImage($image, $mode)
1694 $w = imagesx($image);
1695 $h = imagesy($image);
1696 $flipped = imagecreatetruecolor($w, $h);
1700 for ($y = 0; $y < $h; $y++) {
1701 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1705 for ($x = 0; $x < $w; $x++) {
1706 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1716 * return all assigned tags for the specified photo
1718 private function get_photo_tags($idx)
1720 $result = $this->db->db_query("
1723 INNER JOIN photo_tags pt
1725 WHERE pt.photo_id='". $idx ."'
1730 while($row = $this->db->db_fetch_object($result))
1731 $tags[$row['id']] = $row['name'];
1735 } // get_photo_tags()
1738 * create on-the-fly images with text within
1740 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1742 if (strlen($color) != 6)
1745 $int = hexdec($color);
1746 $h = imagefontheight($font);
1747 $fw = imagefontwidth($font);
1748 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1749 $lines = count($txt);
1750 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1751 $bg = imagecolorallocate($im, 255, 255, 255);
1752 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1755 foreach ($txt as $text) {
1756 $x = (($w - ($fw * strlen($text))) / 2);
1757 imagestring($im, $font, $x, $y, $text, $color);
1758 $y += ($h + $space);
1761 Header("Content-type: image/png");
1764 } // showTextImage()
1767 * check if all requirements are met
1769 private function checkRequirements()
1771 if(!function_exists("imagecreatefromjpeg")) {
1772 print "PHP GD library extension is missing<br />\n";
1776 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1777 print "PHP SQLite3 library extension is missing<br />\n";
1781 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1782 ini_set('track_errors', 1);
1783 @include_once 'HTML/AJAX/Server.php';
1784 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1785 print "PEAR HTML_AJAX package is missing<br />\n";
1788 @include_once 'Calendar/Calendar.php';
1789 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1790 print "PEAR Calendar package is missing<br />\n";
1793 @include_once 'Console/Getopt.php';
1794 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1795 print "PEAR Console_Getopt package is missing<br />\n";
1798 ini_restore('track_errors');
1805 } // checkRequirements()
1807 private function _debug($text)
1809 if($this->fromcmd) {
1816 * check if specified MIME type is supported
1818 public function checkifImageSupported($mime)
1820 if(in_array($mime, Array("image/jpeg", "image/png")))
1825 } // checkifImageSupported()
1827 public function _error($text)
1829 switch($this->cfg->logging) {
1832 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1833 print $text ."<br />\n";
1839 error_log($text, 3, $his->cfg->log_file);
1843 $this->runtime_error = true;
1848 * output calendard input fields
1850 private function get_calendar($mode)
1852 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1853 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1854 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1856 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1857 if(!isset($_SESSION[$mode .'_date']))
1858 $output.= " disabled=\"disabled\"";
1860 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1861 if(!isset($_SESSION[$mode .'_date']))
1862 $output.= " disabled=\"disabled\"";
1864 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1865 if(!isset($_SESSION[$mode .'_date']))
1866 $output.= " disabled=\"disabled\"";
1874 * output calendar matrix
1876 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1878 if (!isset($year)) $year = date('Y');
1879 if (!isset($month)) $month = date('m');
1880 if (!isset($day)) $day = date('d');
1885 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1886 require_once CALENDAR_ROOT.'Day.php';
1889 $month = new Calendar_Month_Weekdays($year,$month);
1892 $prevStamp = $month->prevMonth(true);
1893 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1894 $nextStamp = $month->nextMonth(true);
1895 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1897 $selectedDays = array (
1898 new Calendar_Day($year,$month,$day),
1899 new Calendar_Day($year,12,25),
1902 // Build the days in the month
1903 $month->build($selectedDays);
1905 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1906 $this->tmpl->assign('prev_month', $prev);
1907 $this->tmpl->assign('next_month', $next);
1909 while ( $day = $month->fetch() ) {
1911 if(!isset($matrix[$rows]))
1912 $matrix[$rows] = Array();
1916 $dayStamp = $day->thisDay(true);
1917 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1919 // isFirst() to find start of week
1920 if ( $day->isFirst() )
1923 if ( $day->isSelected() ) {
1924 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1925 } else if ( $day->isEmpty() ) {
1926 $string.= "<td> </td>\n";
1928 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1931 // isLast() to find end of week
1932 if ( $day->isLast() )
1933 $string.= "</tr>\n";
1935 $matrix[$rows][$cols] = $string;
1945 $this->tmpl->assign('matrix', $matrix);
1946 $this->tmpl->assign('rows', $rows);
1947 $this->tmpl->show("calendar.tpl");
1949 } // get_calendar_matrix()
1952 * output export page
1954 public function getExport($mode)
1956 $pictures = $this->getPhotoSelection();
1957 $current_tags = $this->getCurrentTags();
1959 foreach($pictures as $picture) {
1961 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1962 if($current_tags != "") {
1963 $orig_url.= "&tags=". $current_tags;
1965 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1966 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1969 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1974 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1975 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1979 // "[%pictureurl% %thumbnailurl%]"
1980 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1983 case 'MoinMoinList':
1984 // " * [%pictureurl% %thumbnailurl%]"
1985 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1996 public function getRSSFeed()
1998 Header("Content-type: text/xml; charset=utf-8");
1999 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2002 xmlns:media="http://search.yahoo.com/mrss/"
2003 xmlns:dc="http://purl.org/dc/elements/1.1/"
2006 <title>phpfspot</title>
2007 <description>phpfspot RSS feed</description>
2008 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2009 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2010 <generator>phpfspot</generator>
2013 $pictures = $this->getPhotoSelection();
2014 $current_tags = $this->getCurrentTags();
2016 foreach($pictures as $picture) {
2018 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2019 if($current_tags != "") {
2020 $orig_url.= "&tags=". $current_tags;
2022 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2023 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2026 $details = $this->get_photo_details($picture);
2028 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2029 $thumb_html = htmlspecialchars("
2030 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2032 ". $details['description']);
2034 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2036 /* get EXIF information if JPEG */
2037 if($details['mime'] == "image/jpeg") {
2038 $meta = $this->get_meta_informations($orig_path);
2041 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2045 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2046 <link><?php print htmlspecialchars($orig_url); ?></link>
2047 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2048 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2050 <?php print $thumb_html; ?>
2052 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2067 * return all selected tags as one string
2069 private function getCurrentTags()
2072 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2073 foreach($_SESSION['selected_tags'] as $tag)
2074 $current_tags.= $tag .",";
2075 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2077 return $current_tags;
2079 } // getCurrentTags()
2082 * return the current photo
2084 public function getCurrentPhoto()
2086 if(isset($_SESSION['current_photo'])) {
2087 print $_SESSION['current_photo'];
2089 } // getCurrentPhoto()
2092 * tells the client browser what to do
2094 * this function is getting called via AJAX by the
2095 * client browsers. it will tell them what they have
2096 * to do next. This is necessary for directly jumping
2097 * into photo index or single photo view when the are
2098 * requested with specific URLs
2100 public function whatToDo()
2102 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2103 return "show_photo";
2105 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2106 return "showpi_tags";
2108 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2112 return "nothing special";
2117 * return the current process-user
2119 private function getuid()
2121 if($uid = posix_getuid()) {
2122 if($user = posix_getpwuid($uid)) {
2123 return $user['name'];
2132 * returns a select-dropdown box to select photo index sort parameters
2134 public function smarty_sort_select_list($params, &$smarty)
2138 foreach($this->sort_orders as $key => $value) {
2139 $output.= "<option value=\"". $key ."\"";
2140 if($key == $_SESSION['sort_order']) {
2141 $output.= " selected=\"selected\"";
2143 $output.= ">". $value ."</option>";
2148 } // smarty_sort_select_list()
2151 * returns the currently selected sort order
2153 private function get_sort_order()
2155 switch($_SESSION['sort_order']) {
2157 return " ORDER BY p.time ASC";
2160 return " ORDER BY p.time DESC";
2163 if($this->dbver < 9) {
2164 return " ORDER BY p.name ASC";
2167 return " ORDER BY basename(p.uri) ASC";
2171 if($this->dbver < 9) {
2172 return " ORDER BY p.name DESC";
2175 return " ORDER BY basename(p.uri) DESC";
2179 return " ORDER BY t.name ASC ,p.time ASC";
2182 return " ORDER BY t.name DESC ,p.time ASC";
2186 } // get_sort_order()
2189 * return the next to be shown slide show image
2191 * this function returns the URL of the next image
2192 * in the slideshow sequence.
2194 public function getNextSlideShowImage()
2196 $all_photos = $this->getPhotoSelection();
2198 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2199 $_SESSION['slideshow_img'] = 0;
2201 $_SESSION['slideshow_img']++;
2203 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2205 } // getNextSlideShowImage()
2208 * return the previous to be shown slide show image
2210 * this function returns the URL of the previous image
2211 * in the slideshow sequence.
2213 public function getPrevSlideShowImage()
2215 $all_photos = $this->getPhotoSelection();
2217 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2218 $_SESSION['slideshow_img'] = 0;
2220 $_SESSION['slideshow_img']--;
2222 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2224 } // getPrevSlideShowImage()
2226 public function resetSlideShow()
2228 if(isset($_SESSION['slideshow_img']))
2229 unset($_SESSION['slideshow_img']);
2231 } // resetSlideShow()
2236 * this function will get all photos from the fspot
2237 * database and randomly return ONE entry
2239 * saddly there is yet no sqlite3 function which returns
2240 * the bulk result in array, so we have to fill up our
2243 public function get_random_photo()
2247 $result = $this->db->db_query("
2252 while($row = $this->db->db_fetch_object($result)) {
2253 array_push($all, $row['id']);
2256 return $all[array_rand($all)];
2258 } // get_random_photo()
2261 * validates provided date
2263 * this function validates if the provided date
2264 * contains a valid date and will return true
2267 public function isValidDate($date_str)
2269 $timestamp = strtotime($date_str);
2271 if(is_numeric($timestamp))
2279 * timestamp to string conversion
2281 private function ts2str($timestamp)
2283 return strftime("%Y-%m-%d", $timestamp);
2286 private function extractTags($tags_str)
2288 $not_validated = split(',', $_GET['tags']);
2289 $validated = array();
2291 foreach($not_validated as $tag) {
2292 if(is_numeric($tag))
2293 array_push($validated, $tag);
2301 * returns the full path to a thumbnail
2303 public function get_thumb_path($width, $photo)
2305 $md5 = $this->getMD5($photo);
2306 $sub_path = substr($md5, 0, 2);
2307 return $this->cfg->thumb_path
2315 } // get_thumb_path()
2318 * returns server's virtual host name
2320 private function get_server_name()
2322 return $_SERVER['SERVER_NAME'];
2323 } // get_server_name()
2326 * returns type of webprotocol which is
2329 private function get_web_protocol()
2331 if(!isset($_SERVER['HTTPS']))
2335 } // get_web_protocol()
2338 * return url to this phpfspot installation
2340 private function get_phpfspot_url()
2342 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2343 } // get_phpfspot_url()
2346 * returns the number of photos which are tagged with $tag_id
2348 public function get_num_photos($tag_id)
2350 if($result = $this->db->db_fetchSingleRow("
2351 SELECT count(*) as number
2354 tag_id LIKE '". $tag_id ."'")) {
2356 return $result['number'];
2362 } // get_num_photos()
2365 * check file exists and is readable
2367 * returns true, if everything is ok, otherwise false
2368 * if $silent is not set, this function will output and
2371 private function check_readable($file, $silent = null)
2373 if(!file_exists($file)) {
2375 print "File \"". $file ."\" does not exist.\n";
2379 if(!is_readable($file)) {
2381 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2387 } // check_readable()
2390 * check if all needed indices are present
2392 * this function checks, if some needed indices are already
2393 * present, or if not, create them on the fly. they are
2394 * necessary to speed up some queries like that one look for
2395 * all tags, when show_tags is specified in the configuration.
2397 private function checkDbIndices()
2399 $result = $this->db->db_exec("
2400 CREATE INDEX IF NOT EXISTS
2407 } // checkDbIndices()
2410 * retrive F-Spot database version
2412 * this function will return the F-Spot database version number
2413 * It is stored within the sqlite3 database in the table meta
2415 public function getFspotDBVersion()
2417 if($result = $this->db->db_fetchSingleRow("
2418 SELECT data as version
2421 name LIKE 'F-Spot Database Version'
2423 return $result['version'];
2427 } // getFspotDBVersion()
2430 * parse the provided URI and will returned the
2433 public function parse_uri($uri, $mode)
2435 if(($components = parse_url($uri)) !== false) {
2439 return basename($components['path']);
2442 return dirname($components['path']);
2445 return $components['path'];
2455 * validate config options
2457 * this function checks if all necessary configuration options are
2458 * specified and set.
2460 private function check_config_options()
2462 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2463 $this->_error("Please set \$page_title in phpfspot_cfg");
2465 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2466 $this->_error("Please set \$base_path in phpfspot_cfg");
2468 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2469 $this->_error("Please set \$web_path in phpfspot_cfg");
2471 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2472 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2474 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2475 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2477 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2478 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2480 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2481 $this->_error("Please set \$db_access in phpfspot_cfg");
2483 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2484 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2486 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2487 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2489 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2490 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2492 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2493 $this->_error("Please set \$photo_width in phpfspot_cfg");
2495 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2496 $this->_error("Please set \$mini_width in phpfspot_cfg");
2498 if(!isset($this->cfg->thumbs_per_page))
2499 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2501 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2502 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2504 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2505 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2507 if(!isset($this->cfg->hide_tags))
2508 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2510 if(!isset($this->cfg->theme_name))
2511 $this->_error("Please set \$theme_name in phpfspot_cfg");
2513 if(!isset($this->cfg->logging))
2514 $this->_error("Please set \$logging in phpfspot_cfg");
2516 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2518 if(!isset($this->cfg->log_file))
2519 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2521 if(!is_writeable($this->cfg->log_file))
2522 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2526 /* check for pending slash on web_path */
2527 if(!preg_match("/\/$/", $this->cfg->web_path))
2528 $this->cfg->web_path.= "/";
2530 return $this->runtime_error;
2532 } // check_config_options()
2535 * cleanup phpfspot own database
2537 * When photos are getting delete from F-Spot, there will remain
2538 * remain some residues in phpfspot own database. This function
2539 * will try to wipe them out.
2541 public function cleanup_phpfspot_db()
2543 $to_delete = Array();
2545 $result = $this->cfg_db->db_query("
2548 ORDER BY img_idx ASC
2551 while($row = $this->cfg_db->db_fetch_object($result)) {
2552 if(!$this->db->db_fetchSingleRow("
2555 WHERE id='". $row['img_idx'] ."'")) {
2557 array_push($to_delete, $row['img_idx'], ',');
2561 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2563 $this->cfg_db->db_exec("
2565 WHERE img_idx IN (". implode($to_delete) .")
2568 } // cleanup_phpfspot_db()
2571 * return first image of the page, the $current photo
2574 * this function is used to find out the first photo of the
2575 * current page, in which the $current photo lies. this is
2576 * used to display the correct photo, when calling showPhotoIndex()
2579 private function getCurrentPage($current, $max)
2581 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2582 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2583 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2589 } // getCurrentPage()