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 f-spot database meta information */
464 $meta = $this->get_meta_informations($orig_path);
466 /* If EXIF data are available, use them */
467 if(isset($meta['ExifImageWidth'])) {
468 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
470 $info = getimagesize($orig_path);
471 $meta_res = $info[0] ."x". $info[1];
474 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
475 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
476 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
478 $extern_link = "index.php?mode=showp&id=". $photo;
479 $current_tags = $this->getCurrentTags();
480 if($current_tags != "") {
481 $extern_link.= "&tags=". $current_tags;
483 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
484 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
487 $this->tmpl->assign('extern_link', $extern_link);
489 if(!file_exists($thumb_path)) {
490 $this->_error("Can't open file ". $thumb_path ."\n");
494 $info = getimagesize($thumb_path);
496 $this->tmpl->assign('description', $details['description']);
497 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
499 $this->tmpl->assign('width', $info[0]);
500 $this->tmpl->assign('height', $info[1]);
501 $this->tmpl->assign('ExifMadeOn', $meta_date);
502 $this->tmpl->assign('ExifMadeWith', $meta_make);
503 $this->tmpl->assign('ExifOrigResolution', $meta_res);
504 $this->tmpl->assign('ExifFileSize', $meta_size);
506 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
507 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
508 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
510 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
511 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
512 $this->tmpl->assign('current_img', $photo);
515 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
516 $this->tmpl->assign('prev_img', $previous_img);
520 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
521 $this->tmpl->assign('next_img', $next_img);
523 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
524 $this->tmpl->assign('photo_number', $i);
525 $this->tmpl->assign('photo_count', count($all_photos));
527 $this->tmpl->show("single_photo.tpl");
532 * all available tags and tag cloud
534 * this function outputs all available tags (time ordered)
535 * and in addition output them as tag cloud (tags which have
536 * many photos will appears more then others)
538 public function getAvailableTags()
540 /* retrive tags from database */
545 $result = $this->db->db_query("
546 SELECT tag_id as id, count(tag_id) as quantity
556 while($row = $this->db->db_fetch_object($result)) {
557 $tags[$row['id']] = $row['quantity'];
560 // change these font sizes if you will
561 $max_size = 125; // max font size in %
562 $min_size = 75; // min font size in %
564 // get the largest and smallest array values
565 $max_qty = max(array_values($tags));
566 $min_qty = min(array_values($tags));
568 // find the range of values
569 $spread = $max_qty - $min_qty;
570 if (0 == $spread) { // we don't want to divide by zero
574 // determine the font-size increment
575 // this is the increase per tag quantity (times used)
576 $step = ($max_size - $min_size)/($spread);
578 // loop through our tag array
579 foreach ($tags as $key => $value) {
581 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
584 // calculate CSS font-size
585 // find the $value in excess of $min_qty
586 // multiply by the font-size increment ($size)
587 // and add the $min_size set above
588 $size = $min_size + (($value - $min_qty) * $step);
589 // uncomment if you want sizes in whole %:
592 if(isset($this->tags[$key])) {
593 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
598 $output = substr($output, 0, strlen($output)-2);
601 } // getAvailableTags()
604 * output all selected tags
606 * this function output all tags which have been selected
607 * by the user. the selected tags are stored in the
608 * session-variable $_SESSION['selected_tags']
610 public function getSelectedTags()
612 /* retrive tags from database */
617 foreach($this->avail_tags as $tag)
619 // return all selected tags
620 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
621 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
626 $output = substr($output, 0, strlen($output)-2);
630 return "no tags selected";
633 } // getSelectedTags()
636 * add tag to users session variable
638 * this function will add the specified to users current
639 * tag selection. if a date search has been made before
640 * it will be now cleared
642 public function addTag($tag)
644 if(!isset($_SESSION['selected_tags']))
645 $_SESSION['selected_tags'] = Array();
647 if(isset($_SESSION['searchfor_tag']))
648 unset($_SESSION['searchfor_tag']);
650 if(!in_array($tag, $_SESSION['selected_tags']))
651 array_push($_SESSION['selected_tags'], $tag);
659 * remove tag to users session variable
661 * this function removes the specified tag from
662 * users current tag selection
664 public function delTag($tag)
666 if(isset($_SESSION['searchfor_tag']))
667 unset($_SESSION['searchfor_tag']);
669 if(isset($_SESSION['selected_tags'])) {
670 $key = array_search($tag, $_SESSION['selected_tags']);
671 unset($_SESSION['selected_tags'][$key]);
672 sort($_SESSION['selected_tags']);
680 * reset tag selection
682 * if there is any tag selection, it will be
685 public function resetTags()
687 if(isset($_SESSION['selected_tags']))
688 unset($_SESSION['selected_tags']);
693 * returns the value for the autocomplet tag-search
695 public function get_xml_tag_list()
697 if(!isset($_GET['search']) || !is_string($_GET['search']))
698 $_GET['search'] = '';
703 /* retrive tags from database */
706 $matched_tags = Array();
708 header("Content-Type: text/xml");
710 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
711 $string.= "<results>\n";
713 foreach($this->avail_tags as $tag)
715 if(!empty($_GET['search']) &&
716 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
717 count($matched_tags) < $length) {
719 $count = $this->get_num_photos($tag);
722 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
725 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
731 /* if we have collected enough items, break out */
732 if(count($matched_tags) >= $length)
736 $string.= "</results>\n";
740 } // get_xml_tag_list()
746 * if a specific photo was requested (external link)
747 * unset the session variable now
749 public function resetPhotoView()
751 if(isset($_SESSION['current_photo']))
752 unset($_SESSION['current_photo']);
754 } // resetPhotoView();
759 * if any tag search has taken place, reset it now
761 public function resetTagSearch()
763 if(isset($_SESSION['searchfor_tag']))
764 unset($_SESSION['searchfor_tag']);
766 } // resetTagSearch()
771 * if any name search has taken place, reset it now
773 public function resetNameSearch()
775 if(isset($_SESSION['searchfor_name']))
776 unset($_SESSION['searchfor_name']);
778 } // resetNameSearch()
783 * if any date search has taken place, reset
786 public function resetDateSearch()
788 if(isset($_SESSION['from_date']))
789 unset($_SESSION['from_date']);
790 if(isset($_SESSION['to_date']))
791 unset($_SESSION['to_date']);
793 } // resetDateSearch();
796 * return all photo according selection
798 * this function returns all photos based on
799 * the tag-selection, tag- or date-search.
800 * the tag-search also has to take care of AND
801 * and OR conjunctions
803 public function getPhotoSelection()
805 $matched_photos = Array();
806 $additional_where_cond = "";
808 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
809 $from_date = $_SESSION['from_date'];
810 $to_date = $_SESSION['to_date'];
811 $additional_where_cond.= "
812 p.time>='". $from_date ."'
814 p.time<='". $to_date ."'
818 if(isset($_SESSION['searchfor_name'])) {
819 if($this->dbver < 9) {
820 $additional_where_cond.= "
822 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
824 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
829 $additional_where_cond.= "
831 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
833 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
839 if(isset($_SESSION['sort_order'])) {
840 $order_str = $this->get_sort_order();
843 /* return a search result */
844 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
846 SELECT DISTINCT pt1.photo_id
848 INNER JOIN photo_tags pt2
849 ON pt1.photo_id=pt2.photo_id
856 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
858 if(isset($additional_where_cond) && !empty($additional_where_cond))
859 $query_str.= "AND ". $additional_where_cond ." ";
861 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
862 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
865 if(isset($order_str))
866 $query_str.= $order_str;
868 $result = $this->db->db_query($query_str);
869 while($row = $this->db->db_fetch_object($result)) {
870 array_push($matched_photos, $row['photo_id']);
872 return $matched_photos;
875 /* return according the selected tags */
876 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
878 foreach($_SESSION['selected_tags'] as $tag)
879 $selected.= $tag .",";
880 $selected = substr($selected, 0, strlen($selected)-1);
882 /* photo has to match at least on of the selected tags */
883 if($_SESSION['tag_condition'] == 'or') {
885 SELECT DISTINCT pt1.photo_id
887 INNER JOIN photo_tags pt2
888 ON pt1.photo_id=pt2.photo_id
893 WHERE pt1.tag_id IN (". $selected .")
895 if(isset($additional_where_cond) && !empty($additional_where_cond))
896 $query_str.= "AND ". $additional_where_cond ." ";
898 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
899 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
902 if(isset($order_str))
903 $query_str.= $order_str;
905 /* photo has to match all selected tags */
906 elseif($_SESSION['tag_condition'] == 'and') {
908 if(count($_SESSION['selected_tags']) >= 32) {
909 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
910 print "evaluate your tag selection. Please remove some tags from your selection.\n";
914 /* Join together a table looking like
916 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
918 so the query can quickly return all images matching the
919 selected tags in an AND condition
924 SELECT DISTINCT pt1.photo_id
928 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
935 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
937 INNER JOIN photo_tags pt". ($i+2) ."
938 ON pt1.photo_id=pt". ($i+2) .".photo_id
945 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
946 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
948 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
951 if(isset($additional_where_cond) && !empty($additional_where_cond))
952 $query_str.= "AND ". $additional_where_cond;
954 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
955 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
958 if(isset($order_str))
959 $query_str.= $order_str;
963 $result = $this->db->db_query($query_str);
964 while($row = $this->db->db_fetch_object($result)) {
965 array_push($matched_photos, $row['photo_id']);
967 return $matched_photos;
970 /* return all available photos */
974 LEFT JOIN photo_tags pt
980 if(isset($additional_where_cond) && !empty($additional_where_cond))
981 $query_str.= "WHERE ". $additional_where_cond ." ";
983 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
984 if(isset($additional_where_cond) && !empty($additional_where_cond))
985 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
987 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
990 if(isset($order_str))
991 $query_str.= $order_str;
993 $result = $this->db->db_query($query_str);
994 while($row = $this->db->db_fetch_object($result)) {
995 array_push($matched_photos, $row['id']);
997 return $matched_photos;
999 } // getPhotoSelection()
1002 * control HTML ouput for photo index
1004 * this function provides all the necessary information
1005 * for the photo index template.
1007 public function showPhotoIndex()
1009 $photos = $this->getPhotoSelection();
1011 $count = count($photos);
1013 /* if all thumbnails should be shown on one page */
1014 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1018 /* thumbnails should be splitted up in several pages */
1019 elseif($this->cfg->thumbs_per_page > 0) {
1021 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1025 $begin_with = $_SESSION['begin_with'];
1028 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1032 $images[$thumbs] = Array();
1033 $img_height[$thumbs] = Array();
1034 $img_width[$thumbs] = Array();
1035 $img_id[$thumbs] = Array();
1036 $img_name[$thumbs] = Array();
1037 $img_fullname[$thumbs] = Array();
1038 $img_title = Array();
1040 for($i = $begin_with; $i < $end_with; $i++) {
1042 if(isset($photos[$i])) {
1044 $images[$thumbs] = $photos[$i];
1045 $img_id[$thumbs] = $i;
1046 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1047 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1048 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1050 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1052 if(file_exists($thumb_path)) {
1053 $info = getimagesize($thumb_path);
1054 $img_width[$thumbs] = $info[0];
1055 $img_height[$thumbs] = $info[1];
1061 // +1 for for smarty's selection iteration
1064 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1065 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1067 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1068 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1069 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1072 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1073 $this->tmpl->assign('tag_result', 1);
1076 /* do we have to display the page selector ? */
1077 if($this->cfg->thumbs_per_page != 0) {
1081 /* calculate the page switchers */
1082 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1083 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1085 if($begin_with != 0)
1086 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1087 if($end_with < $count)
1088 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1090 $photo_per_page = $this->cfg->thumbs_per_page;
1091 $last_page = ceil($count / $photo_per_page);
1093 /* get the current selected page */
1094 if($begin_with == 0) {
1098 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1105 for($i = 1; $i <= $last_page; $i++) {
1107 if($current_page == $i)
1108 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1109 elseif($current_page-1 == $i || $current_page+1 == $i)
1110 $style = "style=\"font-size: 105%;\"";
1111 elseif(($current_page-5 >= $i) && ($i != 1) ||
1112 ($current_page+5 <= $i) && ($i != $last_page))
1113 $style = "style=\"font-size: 75%;\"";
1117 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1120 $select.= ">". $i ."</a> ";
1122 // until 9 pages we show the selector from 1-9
1123 if($last_page <= 9) {
1124 $page_select.= $select;
1127 if($i == 1 /* first page */ ||
1128 $i == $last_page /* last page */ ||
1129 $i == $current_page /* current page */ ||
1130 $i == ceil($last_page * 0.25) /* first quater */ ||
1131 $i == ceil($last_page * 0.5) /* half */ ||
1132 $i == ceil($last_page * 0.75) /* third quater */ ||
1133 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1134 (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 */ ||
1135 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1136 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1138 $page_select.= $select;
1146 $page_select.= "......... ";
1151 /* only show the page selector if we have more then one page */
1153 $this->tmpl->assign('page_selector', $page_select);
1157 $current_tags = $this->getCurrentTags();
1158 $extern_link = "index.php?mode=showpi";
1159 $rss_link = "index.php?mode=rss";
1160 if($current_tags != "") {
1161 $extern_link.= "&tags=". $current_tags;
1162 $rss_link.= "&tags=". $current_tags;
1164 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1165 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1166 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1169 $export_link = "index.php?mode=export";
1170 $slideshow_link = "index.php?mode=slideshow";
1172 $this->tmpl->assign('extern_link', $extern_link);
1173 $this->tmpl->assign('slideshow_link', $slideshow_link);
1174 $this->tmpl->assign('export_link', $export_link);
1175 $this->tmpl->assign('rss_link', $rss_link);
1176 $this->tmpl->assign('count', $count);
1177 $this->tmpl->assign('width', $this->cfg->thumb_width);
1178 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1179 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1180 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1181 $this->tmpl->assign('images', $images);
1182 $this->tmpl->assign('img_width', $img_width);
1183 $this->tmpl->assign('img_height', $img_height);
1184 $this->tmpl->assign('img_id', $img_id);
1185 $this->tmpl->assign('img_name', $img_name);
1186 $this->tmpl->assign('img_fullname', $img_fullname);
1187 $this->tmpl->assign('img_title', $img_title);
1188 $this->tmpl->assign('thumbs', $thumbs);
1190 $this->tmpl->show("photo_index.tpl");
1192 /* if we are returning to photo index from an photo-view,
1193 scroll the window to the last shown photo-thumbnail.
1194 after this, unset the last_photo session variable.
1196 if(isset($_SESSION['last_photo'])) {
1197 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1198 unset($_SESSION['last_photo']);
1201 } // showPhotoIndex()
1204 * show credit template
1206 public function showCredits()
1208 $this->tmpl->assign('version', $this->cfg->version);
1209 $this->tmpl->assign('product', $this->cfg->product);
1210 $this->tmpl->assign('db_version', $this->dbver);
1211 $this->tmpl->show("credits.tpl");
1216 * create_thumbnails for the requested width
1218 * this function creates image thumbnails of $orig_image
1219 * stored as $thumb_image. It will check if the image is
1220 * in a supported format, if necessary rotate the image
1221 * (based on EXIF orientation meta headers) and re-sizing.
1223 public function create_thumbnail($orig_image, $thumb_image, $width)
1225 if(!file_exists($orig_image)) {
1229 $details = getimagesize($orig_image);
1231 /* check if original photo is a support image type */
1232 if(!$this->checkifImageSupported($details['mime']))
1235 $meta = $this->get_meta_informations($orig_image);
1241 switch($meta['Orientation']) {
1242 case 1: /* top, left */
1243 /* nothing to do */ break;
1244 case 2: /* top, right */
1245 $rotate = 0; $flip_hori = true; break;
1246 case 3: /* bottom, left */
1247 $rotate = 180; break;
1248 case 4: /* bottom, right */
1249 $flip_vert = true; break;
1250 case 5: /* left side, top */
1251 $rotate = 90; $flip_vert = true; break;
1252 case 6: /* right side, top */
1253 $rotate = 90; break;
1254 case 7: /* left side, bottom */
1255 $rotate = 270; $flip_vert = true; break;
1256 case 8: /* right side, bottom */
1257 $rotate = 270; break;
1260 $src_img = @imagecreatefromjpeg($orig_image);
1263 print "Can't load image from ". $orig_image ."\n";
1267 /* grabs the height and width */
1268 $cur_width = imagesx($src_img);
1269 $cur_height = imagesy($src_img);
1271 // If requested width is more then the actual image width,
1272 // do not generate a thumbnail, instead safe the original
1273 // as thumbnail but with lower quality. But if the image
1274 // is to heigh too, then we still have to resize it.
1275 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1276 $result = imagejpeg($src_img, $thumb_image, 75);
1277 imagedestroy($src_img);
1281 // If the image will be rotate because EXIF orientation said so
1282 // 'virtually rotate' the image for further calculations
1283 if($rotate == 90 || $rotate == 270) {
1285 $cur_width = $cur_height;
1289 /* calculates aspect ratio */
1290 $aspect_ratio = $cur_height / $cur_width;
1293 if($aspect_ratio < 1) {
1295 $new_h = abs($new_w * $aspect_ratio);
1297 /* 'virtually' rotate the image and calculate it's ratio */
1298 $tmp_w = $cur_height;
1299 $tmp_h = $cur_width;
1300 /* now get the ratio from the 'rotated' image */
1301 $tmp_ratio = $tmp_h/$tmp_w;
1302 /* now calculate the new dimensions */
1304 $tmp_h = abs($tmp_w * $tmp_ratio);
1306 // now that we know, how high they photo should be, if it
1307 // gets rotated, use this high to scale the image
1309 $new_w = abs($new_h / $aspect_ratio);
1311 // If the image will be rotate because EXIF orientation said so
1312 // now 'virtually rotate' back the image for the image manipulation
1313 if($rotate == 90 || $rotate == 270) {
1320 /* creates new image of that size */
1321 $dst_img = imagecreatetruecolor($new_w, $new_h);
1323 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1325 /* copies resized portion of original image into new image */
1326 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1328 /* needs the image to be flipped horizontal? */
1330 $this->_debug("(FLIP)");
1331 $dst_img = $this->flipImage($dst_img, 'hori');
1333 /* needs the image to be flipped vertical? */
1335 $this->_debug("(FLIP)");
1336 $dst_img = $this->flipImage($dst_img, 'vert');
1340 $this->_debug("(ROTATE)");
1341 $dst_img = $this->rotateImage($dst_img, $rotate);
1344 /* write down new generated file */
1345 $result = imagejpeg($dst_img, $thumb_image, 75);
1347 /* free your mind */
1348 imagedestroy($dst_img);
1349 imagedestroy($src_img);
1351 if($result === false) {
1352 print "Can't write thumbnail ". $thumb_image ."\n";
1358 } // create_thumbnail()
1361 * return all exif meta data from the file
1363 public function get_meta_informations($file)
1365 return exif_read_data($file);
1367 } // get_meta_informations()
1370 * create phpfspot own sqlite database
1372 * this function creates phpfspots own sqlite database
1373 * if it does not exist yet. this own is used to store
1374 * some necessary informations (md5 sum's, ...).
1376 public function check_config_table()
1378 // if the config table doesn't exist yet, create it
1379 if(!$this->cfg_db->db_check_table_exists("images")) {
1380 $this->cfg_db->db_exec("
1381 CREATE TABLE images (
1382 img_idx int primary key,
1388 } // check_config_table
1391 * Generates a thumbnail from photo idx
1393 * This function will generate JPEG thumbnails from provided F-Spot photo
1396 * 1. Check if all thumbnail generations (width) are already in place and
1398 * 2. Check if the md5sum of the original file has changed
1399 * 3. Generate the thumbnails if needed
1401 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1405 $resolutions = Array(
1406 $this->cfg->thumb_width,
1407 $this->cfg->photo_width,
1408 $this->cfg->mini_width,
1411 /* get details from F-Spot's database */
1412 $details = $this->get_photo_details($idx);
1414 /* calculate file MD5 sum */
1415 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1417 if(!file_exists($full_path)) {
1418 $this->_error("File ". $full_path ." does not exist\n");
1422 if(!is_readable($full_path)) {
1423 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1427 $file_md5 = md5_file($full_path);
1429 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1433 foreach($resolutions as $resolution) {
1435 $generate_it = false;
1437 $thumb_sub_path = substr($file_md5, 0, 2);
1438 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1440 /* if thumbnail-subdirectory does not exist yet, create it */
1441 if(!file_exists(dirname($thumb_path))) {
1442 mkdir(dirname($thumb_path), 0755);
1445 /* if the thumbnail file doesn't exist, create it */
1446 if(!file_exists($thumb_path)) {
1447 $generate_it = true;
1449 /* if the file hasn't changed there is no need to regen the thumb */
1450 elseif($file_md5 != $this->getMD5($idx) || $force) {
1451 $generate_it = true;
1454 if($generate_it || $overwrite) {
1456 $this->_debug(" ". $resolution ."px");
1457 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1465 $this->_debug(" already exist");
1468 /* set the new/changed MD5 sum for the current photo */
1470 $this->setMD5($idx, $file_md5);
1473 $this->_debug("\n");
1478 * returns stored md5 sum for a specific photo
1480 * this function queries the phpfspot database for a
1481 * stored MD5 checksum of the specified photo
1483 public function getMD5($idx)
1485 $result = $this->cfg_db->db_query("
1488 WHERE img_idx='". $idx ."'
1494 $img = $this->cfg_db->db_fetch_object($result);
1495 return $img['img_md5'];
1500 * set MD5 sum for the specific photo
1502 private function setMD5($idx, $md5)
1504 $result = $this->cfg_db->db_exec("
1505 REPLACE INTO images (img_idx, img_md5)
1506 VALUES ('". $idx ."', '". $md5 ."')
1512 * store current tag condition
1514 * this function stores the current tag condition
1515 * (AND or OR) in the users session variables
1517 public function setTagCondition($mode)
1519 $_SESSION['tag_condition'] = $mode;
1523 } // setTagCondition()
1526 * invoke tag & date search
1528 * this function will return all matching tags and store
1529 * them in the session variable selected_tags. furthermore
1530 * it also handles the date search.
1531 * getPhotoSelection() will then only return the matching
1534 public function startSearch()
1536 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1537 $from = $_POST['from'];
1539 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1543 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1544 $searchfor_tag = $_POST['for_tag'];
1545 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1548 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1549 $searchfor_name = $_POST['for_name'];
1550 $_SESSION['searchfor_name'] = $_POST['for_name'];
1555 if(isset($from) && !empty($from))
1556 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1558 unset($_SESSION['from_date']);
1560 if(isset($to) && !empty($to))
1561 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1563 unset($_SESSION['to_date']);
1565 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1566 /* new search, reset the current selected tags */
1567 $_SESSION['selected_tags'] = Array();
1568 foreach($this->avail_tags as $tag) {
1569 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1570 array_push($_SESSION['selected_tags'], $tag);
1579 * updates sort order in session variable
1581 * this function is invoked by RPC and will sort the requested
1582 * sort order in the session variable.
1584 public function updateSortOrder($order)
1586 if(isset($this->sort_orders[$order])) {
1587 $_SESSION['sort_order'] = $order;
1591 return "unkown error";
1593 } // updateSortOrder()
1598 * this function rotates the image according the
1601 private function rotateImage($img, $degrees)
1603 if(function_exists("imagerotate")) {
1604 $img = imagerotate($img, $degrees, 0);
1606 function imagerotate($src_img, $angle)
1608 $src_x = imagesx($src_img);
1609 $src_y = imagesy($src_img);
1610 if ($angle == 180) {
1614 elseif ($src_x <= $src_y) {
1618 elseif ($src_x >= $src_y) {
1623 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1624 imagealphablending($rotate, false);
1629 for ($y = 0; $y < ($src_y); $y++) {
1630 for ($x = 0; $x < ($src_x); $x++) {
1631 $color = imagecolorat($src_img, $x, $y);
1632 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1638 for ($y = 0; $y < ($src_y); $y++) {
1639 for ($x = 0; $x < ($src_x); $x++) {
1640 $color = imagecolorat($src_img, $x, $y);
1641 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1647 for ($y = 0; $y < ($src_y); $y++) {
1648 for ($x = 0; $x < ($src_x); $x++) {
1649 $color = imagecolorat($src_img, $x, $y);
1650 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1664 $img = imagerotate($img, $degrees);
1673 * returns flipped image
1675 * this function will return an either horizontal or
1676 * vertical flipped truecolor image.
1678 private function flipImage($image, $mode)
1680 $w = imagesx($image);
1681 $h = imagesy($image);
1682 $flipped = imagecreatetruecolor($w, $h);
1686 for ($y = 0; $y < $h; $y++) {
1687 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1691 for ($x = 0; $x < $w; $x++) {
1692 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1702 * return all assigned tags for the specified photo
1704 private function get_photo_tags($idx)
1706 $result = $this->db->db_query("
1709 INNER JOIN photo_tags pt
1711 WHERE pt.photo_id='". $idx ."'
1716 while($row = $this->db->db_fetch_object($result))
1717 $tags[$row['id']] = $row['name'];
1721 } // get_photo_tags()
1724 * create on-the-fly images with text within
1726 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1728 if (strlen($color) != 6)
1731 $int = hexdec($color);
1732 $h = imagefontheight($font);
1733 $fw = imagefontwidth($font);
1734 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1735 $lines = count($txt);
1736 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1737 $bg = imagecolorallocate($im, 255, 255, 255);
1738 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1741 foreach ($txt as $text) {
1742 $x = (($w - ($fw * strlen($text))) / 2);
1743 imagestring($im, $font, $x, $y, $text, $color);
1744 $y += ($h + $space);
1747 Header("Content-type: image/png");
1750 } // showTextImage()
1753 * check if all requirements are met
1755 private function checkRequirements()
1757 if(!function_exists("imagecreatefromjpeg")) {
1758 print "PHP GD library extension is missing<br />\n";
1762 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1763 print "PHP SQLite3 library extension is missing<br />\n";
1767 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1768 ini_set('track_errors', 1);
1769 @include_once 'HTML/AJAX/Server.php';
1770 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1771 print "PEAR HTML_AJAX package is missing<br />\n";
1774 @include_once 'Calendar/Calendar.php';
1775 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1776 print "PEAR Calendar package is missing<br />\n";
1779 @include_once 'Console/Getopt.php';
1780 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1781 print "PEAR Console_Getopt package is missing<br />\n";
1784 ini_restore('track_errors');
1791 } // checkRequirements()
1793 private function _debug($text)
1795 if($this->fromcmd) {
1802 * check if specified MIME type is supported
1804 public function checkifImageSupported($mime)
1806 if(in_array($mime, Array("image/jpeg")))
1811 } // checkifImageSupported()
1813 public function _error($text)
1815 switch($this->cfg->logging) {
1818 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1819 print $text ."<br />\n";
1825 error_log($text, 3, $his->cfg->log_file);
1829 $this->runtime_error = true;
1834 * output calendard input fields
1836 private function get_calendar($mode)
1838 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1839 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1840 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1842 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1843 if(!isset($_SESSION[$mode .'_date']))
1844 $output.= " disabled=\"disabled\"";
1846 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1847 if(!isset($_SESSION[$mode .'_date']))
1848 $output.= " disabled=\"disabled\"";
1850 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1851 if(!isset($_SESSION[$mode .'_date']))
1852 $output.= " disabled=\"disabled\"";
1860 * output calendar matrix
1862 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1864 if (!isset($year)) $year = date('Y');
1865 if (!isset($month)) $month = date('m');
1866 if (!isset($day)) $day = date('d');
1871 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1872 require_once CALENDAR_ROOT.'Day.php';
1875 $month = new Calendar_Month_Weekdays($year,$month);
1878 $prevStamp = $month->prevMonth(true);
1879 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1880 $nextStamp = $month->nextMonth(true);
1881 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1883 $selectedDays = array (
1884 new Calendar_Day($year,$month,$day),
1885 new Calendar_Day($year,12,25),
1888 // Build the days in the month
1889 $month->build($selectedDays);
1891 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1892 $this->tmpl->assign('prev_month', $prev);
1893 $this->tmpl->assign('next_month', $next);
1895 while ( $day = $month->fetch() ) {
1897 if(!isset($matrix[$rows]))
1898 $matrix[$rows] = Array();
1902 $dayStamp = $day->thisDay(true);
1903 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1905 // isFirst() to find start of week
1906 if ( $day->isFirst() )
1909 if ( $day->isSelected() ) {
1910 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1911 } else if ( $day->isEmpty() ) {
1912 $string.= "<td> </td>\n";
1914 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1917 // isLast() to find end of week
1918 if ( $day->isLast() )
1919 $string.= "</tr>\n";
1921 $matrix[$rows][$cols] = $string;
1931 $this->tmpl->assign('matrix', $matrix);
1932 $this->tmpl->assign('rows', $rows);
1933 $this->tmpl->show("calendar.tpl");
1935 } // get_calendar_matrix()
1938 * output export page
1940 public function getExport($mode)
1942 $pictures = $this->getPhotoSelection();
1943 $current_tags = $this->getCurrentTags();
1945 foreach($pictures as $picture) {
1947 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1948 if($current_tags != "") {
1949 $orig_url.= "&tags=". $current_tags;
1951 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1952 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1955 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1960 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1961 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1965 // "[%pictureurl% %thumbnailurl%]"
1966 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1969 case 'MoinMoinList':
1970 // " * [%pictureurl% %thumbnailurl%]"
1971 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1982 public function getRSSFeed()
1984 Header("Content-type: text/xml; charset=utf-8");
1985 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1988 xmlns:media="http://search.yahoo.com/mrss/"
1989 xmlns:dc="http://purl.org/dc/elements/1.1/"
1992 <title>phpfspot</title>
1993 <description>phpfspot RSS feed</description>
1994 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1995 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1996 <generator>phpfspot</generator>
1999 $pictures = $this->getPhotoSelection();
2000 $current_tags = $this->getCurrentTags();
2002 foreach($pictures as $picture) {
2004 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2005 if($current_tags != "") {
2006 $orig_url.= "&tags=". $current_tags;
2008 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2009 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2012 $details = $this->get_photo_details($picture);
2014 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2015 $thumb_html = htmlspecialchars("
2016 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2018 ". $details['description']);
2020 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2021 $meta = $this->get_meta_informations($orig_path);
2022 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2026 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2027 <link><?php print htmlspecialchars($orig_url); ?></link>
2028 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2029 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2031 <?php print $thumb_html; ?>
2033 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2048 * return all selected tags as one string
2050 private function getCurrentTags()
2053 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2054 foreach($_SESSION['selected_tags'] as $tag)
2055 $current_tags.= $tag .",";
2056 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2058 return $current_tags;
2060 } // getCurrentTags()
2063 * return the current photo
2065 public function getCurrentPhoto()
2067 if(isset($_SESSION['current_photo'])) {
2068 print $_SESSION['current_photo'];
2070 } // getCurrentPhoto()
2073 * tells the client browser what to do
2075 * this function is getting called via AJAX by the
2076 * client browsers. it will tell them what they have
2077 * to do next. This is necessary for directly jumping
2078 * into photo index or single photo view when the are
2079 * requested with specific URLs
2081 public function whatToDo()
2083 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2084 return "show_photo";
2086 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2087 return "showpi_tags";
2089 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2093 return "nothing special";
2098 * return the current process-user
2100 private function getuid()
2102 if($uid = posix_getuid()) {
2103 if($user = posix_getpwuid($uid)) {
2104 return $user['name'];
2113 * returns a select-dropdown box to select photo index sort parameters
2115 public function smarty_sort_select_list($params, &$smarty)
2119 foreach($this->sort_orders as $key => $value) {
2120 $output.= "<option value=\"". $key ."\"";
2121 if($key == $_SESSION['sort_order']) {
2122 $output.= " selected=\"selected\"";
2124 $output.= ">". $value ."</option>";
2129 } // smarty_sort_select_list()
2132 * returns the currently selected sort order
2134 private function get_sort_order()
2136 switch($_SESSION['sort_order']) {
2138 return " ORDER BY p.time ASC";
2141 return " ORDER BY p.time DESC";
2144 if($this->dbver < 9) {
2145 return " ORDER BY p.name ASC";
2148 return " ORDER BY basename(p.uri) ASC";
2152 if($this->dbver < 9) {
2153 return " ORDER BY p.name DESC";
2156 return " ORDER BY basename(p.uri) DESC";
2160 return " ORDER BY t.name ASC ,p.time ASC";
2163 return " ORDER BY t.name DESC ,p.time ASC";
2167 } // get_sort_order()
2170 * return the next to be shown slide show image
2172 * this function returns the URL of the next image
2173 * in the slideshow sequence.
2175 public function getNextSlideShowImage()
2177 $all_photos = $this->getPhotoSelection();
2179 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2180 $_SESSION['slideshow_img'] = 0;
2182 $_SESSION['slideshow_img']++;
2184 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2186 } // getNextSlideShowImage()
2189 * return the previous to be shown slide show image
2191 * this function returns the URL of the previous image
2192 * in the slideshow sequence.
2194 public function getPrevSlideShowImage()
2196 $all_photos = $this->getPhotoSelection();
2198 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
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 } // getPrevSlideShowImage()
2207 public function resetSlideShow()
2209 if(isset($_SESSION['slideshow_img']))
2210 unset($_SESSION['slideshow_img']);
2212 } // resetSlideShow()
2217 * this function will get all photos from the fspot
2218 * database and randomly return ONE entry
2220 * saddly there is yet no sqlite3 function which returns
2221 * the bulk result in array, so we have to fill up our
2224 public function get_random_photo()
2228 $result = $this->db->db_query("
2233 while($row = $this->db->db_fetch_object($result)) {
2234 array_push($all, $row['id']);
2237 return $all[array_rand($all)];
2239 } // get_random_photo()
2242 * validates provided date
2244 * this function validates if the provided date
2245 * contains a valid date and will return true
2248 public function isValidDate($date_str)
2250 $timestamp = strtotime($date_str);
2252 if(is_numeric($timestamp))
2260 * timestamp to string conversion
2262 private function ts2str($timestamp)
2264 return strftime("%Y-%m-%d", $timestamp);
2267 private function extractTags($tags_str)
2269 $not_validated = split(',', $_GET['tags']);
2270 $validated = array();
2272 foreach($not_validated as $tag) {
2273 if(is_numeric($tag))
2274 array_push($validated, $tag);
2282 * returns the full path to a thumbnail
2284 public function get_thumb_path($width, $photo)
2286 $md5 = $this->getMD5($photo);
2287 $sub_path = substr($md5, 0, 2);
2288 return $this->cfg->thumb_path
2296 } // get_thumb_path()
2299 * returns server's virtual host name
2301 private function get_server_name()
2303 return $_SERVER['SERVER_NAME'];
2304 } // get_server_name()
2307 * returns type of webprotocol which is
2310 private function get_web_protocol()
2312 if(!isset($_SERVER['HTTPS']))
2316 } // get_web_protocol()
2319 * return url to this phpfspot installation
2321 private function get_phpfspot_url()
2323 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2324 } // get_phpfspot_url()
2327 * returns the number of photos which are tagged with $tag_id
2329 public function get_num_photos($tag_id)
2331 if($result = $this->db->db_fetchSingleRow("
2332 SELECT count(*) as number
2335 tag_id LIKE '". $tag_id ."'")) {
2337 return $result['number'];
2343 } // get_num_photos()
2346 * check file exists and is readable
2348 * returns true, if everything is ok, otherwise false
2349 * if $silent is not set, this function will output and
2352 private function check_readable($file, $silent = null)
2354 if(!file_exists($file)) {
2356 print "File \"". $file ."\" does not exist.\n";
2360 if(!is_readable($file)) {
2362 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2368 } // check_readable()
2371 * check if all needed indices are present
2373 * this function checks, if some needed indices are already
2374 * present, or if not, create them on the fly. they are
2375 * necessary to speed up some queries like that one look for
2376 * all tags, when show_tags is specified in the configuration.
2378 private function checkDbIndices()
2380 $result = $this->db->db_exec("
2381 CREATE INDEX IF NOT EXISTS
2388 } // checkDbIndices()
2391 * retrive F-Spot database version
2393 * this function will return the F-Spot database version number
2394 * It is stored within the sqlite3 database in the table meta
2396 public function getFspotDBVersion()
2398 if($result = $this->db->db_fetchSingleRow("
2399 SELECT data as version
2402 name LIKE 'F-Spot Database Version'
2404 return $result['version'];
2408 } // getFspotDBVersion()
2411 * parse the provided URI and will returned the
2414 public function parse_uri($uri, $mode)
2416 if(($components = parse_url($uri)) !== false) {
2420 return basename($components['path']);
2423 return dirname($components['path']);
2426 return $components['path'];
2436 * validate config options
2438 * this function checks if all necessary configuration options are
2439 * specified and set.
2441 private function check_config_options()
2443 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2444 $this->_error("Please set \$page_title in phpfspot_cfg");
2446 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2447 $this->_error("Please set \$base_path in phpfspot_cfg");
2449 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2450 $this->_error("Please set \$web_path in phpfspot_cfg");
2452 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2453 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2455 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2456 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2458 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2459 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2461 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2462 $this->_error("Please set \$db_access in phpfspot_cfg");
2464 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2465 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2467 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2468 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2470 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2471 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2473 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2474 $this->_error("Please set \$photo_width in phpfspot_cfg");
2476 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2477 $this->_error("Please set \$mini_width in phpfspot_cfg");
2479 if(!isset($this->cfg->thumbs_per_page))
2480 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2482 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2483 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2485 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2486 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2488 if(!isset($this->cfg->hide_tags))
2489 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2491 if(!isset($this->cfg->theme_name))
2492 $this->_error("Please set \$theme_name in phpfspot_cfg");
2494 if(!isset($this->cfg->logging))
2495 $this->_error("Please set \$logging in phpfspot_cfg");
2497 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2499 if(!isset($this->cfg->log_file))
2500 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2502 if(!is_writeable($this->cfg->log_file))
2503 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2507 /* check for pending slash on web_path */
2508 if(!preg_match("/\/$/", $this->cfg->web_path))
2509 $this->cfg->web_path.= "/";
2511 return $this->runtime_error;
2513 } // check_config_options()
2516 * cleanup phpfspot own database
2518 * When photos are getting delete from F-Spot, there will remain
2519 * remain some residues in phpfspot own database. This function
2520 * will try to wipe them out.
2522 public function cleanup_phpfspot_db()
2524 $to_delete = Array();
2526 $result = $this->cfg_db->db_query("
2529 ORDER BY img_idx ASC
2532 while($row = $this->cfg_db->db_fetch_object($result)) {
2533 if(!$this->db->db_fetchSingleRow("
2536 WHERE id='". $row['img_idx'] ."'")) {
2538 array_push($to_delete, $row['img_idx'], ',');
2542 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2544 $this->cfg_db->db_exec("
2546 WHERE img_idx IN (". implode($to_delete) .")
2549 } // cleanup_phpfspot_db()
2552 * return first image of the page, the $current photo
2555 * this function is used to find out the first photo of the
2556 * current page, in which the $current photo lies. this is
2557 * used to display the correct photo, when calling showPhotoIndex()
2560 private function getCurrentPage($current, $max)
2562 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2563 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2564 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2570 } // getCurrentPage()