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.3";
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 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
92 if(!is_writeable($this->cfg->phpfspot_db)) {
93 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
97 $this->check_config_table();
99 /* include Smarty template engine */
100 if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
103 require $this->cfg->smarty_path .'/libs/Smarty.class.php';
104 /* overload Smarty class if our own template handler */
105 require_once "phpfspot_tmpl.php";
106 $this->tmpl = new PHPFSPOT_TMPL($this);
108 /* check if all necessary indices exist */
109 $this->checkDbIndices();
111 /* if session is not yet started, do it now */
112 if(session_id() == "")
115 if(!isset($_SESSION['tag_condition']))
116 $_SESSION['tag_condition'] = 'or';
118 if(!isset($_SESSION['sort_order']))
119 $_SESSION['sort_order'] = 'date_desc';
121 if(!isset($_SESSION['searchfor_tag']))
122 $_SESSION['searchfor_tag'] = '';
124 // if begin_with is still set but thumbs_per_page is now 0, unset it
125 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
126 unset($_SESSION['begin_with']);
130 public function __destruct()
136 * show - generate html output
138 * this function can be called after the constructor has
139 * prepared everyhing. it will load the index.tpl smarty
140 * template. if necessary it will registere pre-selects
141 * (photo index, photo, tag search, date search) into
144 public function show()
146 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
147 $this->tmpl->assign('page_title', $this->cfg->page_title);
148 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
149 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
151 if(isset($_GET['mode'])) {
153 $_SESSION['start_action'] = $_GET['mode'];
155 switch($_GET['mode']) {
157 if(isset($_GET['tags'])) {
158 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
160 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
161 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
163 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
164 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
168 if(isset($_GET['tags'])) {
169 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
170 $_SESSION['start_action'] = 'showp';
172 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
173 $_SESSION['current_photo'] = $_GET['id'];
174 $_SESSION['start_action'] = 'showp';
176 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
177 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
179 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
180 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
184 $this->tmpl->show("export.tpl");
188 $this->tmpl->show("slideshow.tpl");
192 if(isset($_GET['tags'])) {
193 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
195 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
196 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
198 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
199 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
207 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
208 $this->tmpl->assign('date_search_enabled', true);
210 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
211 $this->tmpl->assign('from_date', $this->get_calendar('from'));
212 $this->tmpl->assign('to_date', $this->get_calendar('to'));
213 $this->tmpl->assign('content_page', 'welcome.tpl');
214 $this->tmpl->show("index.tpl");
219 * get_tags - grab all tags of f-spot's database
221 * this function will get all available tags from
222 * the f-spot database and store them within two
223 * arrays within this class for later usage. in
224 * fact, if the user requests (hide_tags) it will
225 * opt-out some of them.
227 * this function is getting called once by show()
229 private function get_tags()
231 $this->avail_tags = Array();
234 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
237 DISTINCT t1.id as id, t1.name as name
240 INNER JOIN photo_tags
241 pt2 ON pt1.photo_id=pt2.photo_id
247 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
249 t1.sort_priority ASC";
251 $result = $this->db->db_query($query_str);
255 $result = $this->db->db_query("
258 ORDER BY sort_priority ASC
262 while($row = $this->db->db_fetch_object($result)) {
264 $tag_id = $row['id'];
265 $tag_name = $row['name'];
267 /* if the user has specified to ignore this tag in phpfspot's
268 configuration, ignore it here so it does not get added to
271 if(in_array($row['name'], $this->cfg->hide_tags))
274 /* if you include the following if-clause and the user has specified
275 to only show certain tags which are specified in phpfspot's
276 configuration, ignore all others so they will not be added to the
278 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
279 !in_array($row['name'], $this->cfg->show_tags))
283 $this->tags[$tag_id] = $tag_name;
284 $this->avail_tags[$count] = $tag_id;
292 * extract all photo details
294 * retrieve all available details from f-spot's
295 * database and return them as object
297 public function get_photo_details($idx)
299 if($this->dbver < 9) {
301 SELECT p.id, p.name, p.time, p.directory_path, p.description
307 SELECT p.id, p.uri, p.time, p.description
312 /* if show_tags is set, only return details for photos which
313 are specified to be shown
315 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
317 INNER JOIN photo_tags pt
321 WHERE p.id='". $idx ."'
322 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
326 WHERE p.id='". $idx ."'
330 if($result = $this->db->db_query($query_str)) {
332 $row = $this->db->db_fetch_object($result);
334 if($this->dbver < 9) {
335 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
344 } // get_photo_details
347 * returns aligned photo names
349 * this function returns aligned (length) names for
350 * an specific photo. If the length of the name exceeds
351 * $limit the name will be shrinked (...)
353 public function getPhotoName($idx, $limit = 0)
355 if($details = $this->get_photo_details($idx)) {
356 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
357 $name = $this->shrink_text($long_name, $limit);
367 * shrink text according provided limit
369 * If the length of the name exceeds $limit the
370 * text will be shortend and some content in between
371 * will be replaced with "..."
373 private function shrink_text($text, $limit)
375 if($limit != 0 && strlen($text) > $limit) {
376 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
384 * translate f-spoth photo path
386 * as the full-qualified path recorded in the f-spot database
387 * is usally not the same as on the webserver, this function
388 * will replace the path with that one specified in the cfg
390 public function translate_path($path, $width = 0)
392 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
397 * control HTML ouput for a single photo
399 * this function provides all the necessary information
400 * for the single photo template.
402 public function showPhoto($photo)
404 /* get all photos from the current photo selection */
405 $all_photos = $this->getPhotoSelection();
406 $count = count($all_photos);
408 for($i = 0; $i < $count; $i++) {
410 // $get_next will be set, when the photo which has to
411 // be displayed has been found - this means that the
412 // next available is in fact the NEXT image (for the
414 if(isset($get_next)) {
415 $next_img = $all_photos[$i];
419 /* the next photo is our NEXT photo */
420 if($all_photos[$i] == $photo) {
424 $previous_img = $all_photos[$i];
427 if($photo == $all_photos[$i]) {
432 $details = $this->get_photo_details($photo);
439 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
440 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
442 if(!file_exists($orig_path)) {
443 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
447 if(!is_readable($orig_path)) {
448 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
452 /* If the thumbnail doesn't exist yet, try to create it */
453 if(!file_exists($thumb_path)) {
454 $this->gen_thumb($photo, true);
455 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
458 /* get f-spot database meta information */
459 $meta = $this->get_meta_informations($orig_path);
461 /* If EXIF data are available, use them */
462 if(isset($meta['ExifImageWidth'])) {
463 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
465 $info = getimagesize($orig_path);
466 $meta_res = $info[0] ."x". $info[1];
469 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
470 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
471 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
473 $extern_link = "index.php?mode=showp&id=". $photo;
474 $current_tags = $this->getCurrentTags();
475 if($current_tags != "") {
476 $extern_link.= "&tags=". $current_tags;
478 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
479 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
482 $this->tmpl->assign('extern_link', $extern_link);
484 if(!file_exists($thumb_path)) {
485 $this->_error("Can't open file ". $thumb_path ."\n");
489 $info = getimagesize($thumb_path);
491 $this->tmpl->assign('description', $details['description']);
492 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
494 $this->tmpl->assign('width', $info[0]);
495 $this->tmpl->assign('height', $info[1]);
496 $this->tmpl->assign('ExifMadeOn', $meta_date);
497 $this->tmpl->assign('ExifMadeWith', $meta_make);
498 $this->tmpl->assign('ExifOrigResolution', $meta_res);
499 $this->tmpl->assign('ExifFileSize', $meta_size);
501 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
502 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
503 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
505 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
506 $this->tmpl->assign('current', $current);
509 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
510 $this->tmpl->assign('prev_img', $previous_img);
514 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
515 $this->tmpl->assign('next_img', $next_img);
517 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
518 $this->tmpl->assign('photo_number', $i);
519 $this->tmpl->assign('photo_count', count($all_photos));
521 $this->tmpl->show("single_photo.tpl");
523 print "<script language=\"JavaScript\">self.location.hash = '#photo';</script>\n";
528 * all available tags and tag cloud
530 * this function outputs all available tags (time ordered)
531 * and in addition output them as tag cloud (tags which have
532 * many photos will appears more then others)
534 public function getAvailableTags()
536 /* retrive tags from database */
541 $result = $this->db->db_query("
542 SELECT tag_id as id, count(tag_id) as quantity
552 while($row = $this->db->db_fetch_object($result)) {
553 $tags[$row['id']] = $row['quantity'];
556 // change these font sizes if you will
557 $max_size = 125; // max font size in %
558 $min_size = 75; // min font size in %
560 // get the largest and smallest array values
561 $max_qty = max(array_values($tags));
562 $min_qty = min(array_values($tags));
564 // find the range of values
565 $spread = $max_qty - $min_qty;
566 if (0 == $spread) { // we don't want to divide by zero
570 // determine the font-size increment
571 // this is the increase per tag quantity (times used)
572 $step = ($max_size - $min_size)/($spread);
574 // loop through our tag array
575 foreach ($tags as $key => $value) {
577 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
580 // calculate CSS font-size
581 // find the $value in excess of $min_qty
582 // multiply by the font-size increment ($size)
583 // and add the $min_size set above
584 $size = $min_size + (($value - $min_qty) * $step);
585 // uncomment if you want sizes in whole %:
588 if(isset($this->tags[$key])) {
589 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
594 $output = substr($output, 0, strlen($output)-2);
597 } // getAvailableTags()
600 * output all selected tags
602 * this function output all tags which have been selected
603 * by the user. the selected tags are stored in the
604 * session-variable $_SESSION['selected_tags']
606 public function getSelectedTags()
608 /* retrive tags from database */
613 foreach($this->avail_tags as $tag)
615 // return all selected tags
616 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
617 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
622 $output = substr($output, 0, strlen($output)-2);
626 return "no tags selected";
629 } // getSelectedTags()
632 * add tag to users session variable
634 * this function will add the specified to users current
635 * tag selection. if a date search has been made before
636 * it will be now cleared
638 public function addTag($tag)
640 if(!isset($_SESSION['selected_tags']))
641 $_SESSION['selected_tags'] = Array();
643 if(isset($_SESSION['searchfor_tag']))
644 unset($_SESSION['searchfor_tag']);
646 if(!in_array($tag, $_SESSION['selected_tags']))
647 array_push($_SESSION['selected_tags'], $tag);
655 * remove tag to users session variable
657 * this function removes the specified tag from
658 * users current tag selection
660 public function delTag($tag)
662 if(isset($_SESSION['searchfor_tag']))
663 unset($_SESSION['searchfor_tag']);
665 if(isset($_SESSION['selected_tags'])) {
666 $key = array_search($tag, $_SESSION['selected_tags']);
667 unset($_SESSION['selected_tags'][$key]);
668 sort($_SESSION['selected_tags']);
676 * reset tag selection
678 * if there is any tag selection, it will be
681 public function resetTags()
683 if(isset($_SESSION['selected_tags']))
684 unset($_SESSION['selected_tags']);
691 * if a specific photo was requested (external link)
692 * unset the session variable now
694 public function resetPhotoView()
696 if(isset($_SESSION['current_photo']))
697 unset($_SESSION['current_photo']);
699 } // resetPhotoView();
704 * if any tag search has taken place, reset it now
706 public function resetTagSearch()
708 if(isset($_SESSION['searchfor_tag']))
709 unset($_SESSION['searchfor_tag']);
711 } // resetTagSearch()
716 * if any name search has taken place, reset it now
718 public function resetNameSearch()
720 if(isset($_SESSION['searchfor_name']))
721 unset($_SESSION['searchfor_name']);
723 } // resetNameSearch()
728 * if any date search has taken place, reset
731 public function resetDateSearch()
733 if(isset($_SESSION['from_date']))
734 unset($_SESSION['from_date']);
735 if(isset($_SESSION['to_date']))
736 unset($_SESSION['to_date']);
738 } // resetDateSearch();
741 * return all photo according selection
743 * this function returns all photos based on
744 * the tag-selection, tag- or date-search.
745 * the tag-search also has to take care of AND
746 * and OR conjunctions
748 public function getPhotoSelection()
750 $matched_photos = Array();
752 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
753 $from_date = $_SESSION['from_date'];
754 $to_date = $_SESSION['to_date'];
755 $additional_where_cond = "
756 p.time>='". $from_date ."'
758 p.time<='". $to_date ."'
762 if(isset($_SESSION['searchfor_name'])) {
763 if($this->dbver < 9) {
764 $additional_where_cond.= "
766 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
768 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
773 $additional_where_cond.= "
775 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
777 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
783 if(isset($_SESSION['sort_order'])) {
784 $order_str = $this->get_sort_order();
787 /* return a search result */
788 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
790 SELECT DISTINCT pt1.photo_id
792 INNER JOIN photo_tags pt2
793 ON pt1.photo_id=pt2.photo_id
800 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
802 if(isset($additional_where_cond))
803 $query_str.= "AND ". $additional_where_cond ." ";
805 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
806 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
809 if(isset($order_str))
810 $query_str.= $order_str;
812 $result = $this->db->db_query($query_str);
813 while($row = $this->db->db_fetch_object($result)) {
814 array_push($matched_photos, $row['photo_id']);
816 return $matched_photos;
819 /* return according the selected tags */
820 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
822 foreach($_SESSION['selected_tags'] as $tag)
823 $selected.= $tag .",";
824 $selected = substr($selected, 0, strlen($selected)-1);
826 /* photo has to match at least on of the selected tags */
827 if($_SESSION['tag_condition'] == 'or') {
829 SELECT DISTINCT pt1.photo_id
831 INNER JOIN photo_tags pt2
832 ON pt1.photo_id=pt2.photo_id
837 WHERE pt1.tag_id IN (". $selected .")
839 if(isset($additional_where_cond))
840 $query_str.= "AND ". $additional_where_cond ." ";
842 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
843 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
846 if(isset($order_str))
847 $query_str.= $order_str;
849 /* photo has to match all selected tags */
850 elseif($_SESSION['tag_condition'] == 'and') {
852 if(count($_SESSION['selected_tags']) >= 32) {
853 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
854 print "evaluate your tag selection. Please remove some tags from your selection.\n";
858 /* Join together a table looking like
860 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
862 so the query can quickly return all images matching the
863 selected tags in an AND condition
868 SELECT DISTINCT pt1.photo_id
872 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
879 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
881 INNER JOIN photo_tags pt". ($i+2) ."
882 ON pt1.photo_id=pt". ($i+2) .".photo_id
889 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
890 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
892 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
895 if(isset($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;
907 $result = $this->db->db_query($query_str);
908 while($row = $this->db->db_fetch_object($result)) {
909 array_push($matched_photos, $row['photo_id']);
911 return $matched_photos;
914 /* return all available photos */
918 LEFT JOIN photo_tags pt
924 if(isset($additional_where_cond))
925 $query_str.= "WHERE ". $additional_where_cond ." ";
927 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
928 if(isset($additional_where_cond))
929 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
931 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
934 if(isset($order_str))
935 $query_str.= $order_str;
937 $result = $this->db->db_query($query_str);
938 while($row = $this->db->db_fetch_object($result)) {
939 array_push($matched_photos, $row['id']);
941 return $matched_photos;
943 } // getPhotoSelection()
946 * control HTML ouput for photo index
948 * this function provides all the necessary information
949 * for the photo index template.
951 public function showPhotoIndex()
953 $photos = $this->getPhotoSelection();
955 $count = count($photos);
957 if(isset($_SESSION['begin_with']) && $_SESSION['begin_with'] != "")
958 $anchor = $_SESSION['begin_with'];
960 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
966 elseif($this->cfg->thumbs_per_page > 0) {
968 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
972 $begin_with = $_SESSION['begin_with'];
975 $end_with = $begin_with + $this->cfg->thumbs_per_page;
979 $images[$thumbs] = Array();
980 $img_height[$thumbs] = Array();
981 $img_width[$thumbs] = Array();
982 $img_id[$thumbs] = Array();
983 $img_name[$thumbs] = Array();
984 $img_title = Array();
986 for($i = $begin_with; $i < $end_with; $i++) {
988 if(isset($photos[$i])) {
990 $images[$thumbs] = $photos[$i];
991 $img_id[$thumbs] = $i;
992 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
993 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
995 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
997 if(file_exists($thumb_path)) {
998 $info = getimagesize($thumb_path);
999 $img_width[$thumbs] = $info[0];
1000 $img_height[$thumbs] = $info[1];
1006 // +1 for for smarty's selection iteration
1009 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1010 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1012 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1013 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1014 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1017 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1018 $this->tmpl->assign('tag_result', 1);
1021 /* do we have to display the page selector ? */
1022 if($this->cfg->thumbs_per_page != 0) {
1026 /* calculate the page switchers */
1027 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1028 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1030 if($begin_with != 0)
1031 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1032 if($end_with < $count)
1033 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1035 $photo_per_page = $this->cfg->thumbs_per_page;
1036 $last_page = ceil($count / $photo_per_page);
1038 /* get the current selected page */
1039 if($begin_with == 0) {
1043 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1050 for($i = 1; $i <= $last_page; $i++) {
1052 if($current_page == $i)
1053 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1054 elseif($current_page-1 == $i || $current_page+1 == $i)
1055 $style = "style=\"font-size: 105%;\"";
1056 elseif(($current_page-5 >= $i) && ($i != 1) ||
1057 ($current_page+5 <= $i) && ($i != $last_page))
1058 $style = "style=\"font-size: 75%;\"";
1062 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1065 $select.= ">". $i ."</a> ";
1067 // until 9 pages we show the selector from 1-9
1068 if($last_page <= 9) {
1069 $page_select.= $select;
1072 if($i == 1 /* first page */ ||
1073 $i == $last_page /* last page */ ||
1074 $i == $current_page /* current page */ ||
1075 $i == ceil($last_page * 0.25) /* first quater */ ||
1076 $i == ceil($last_page * 0.5) /* half */ ||
1077 $i == ceil($last_page * 0.75) /* third quater */ ||
1078 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1079 (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 */ ||
1080 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1081 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1083 $page_select.= $select;
1091 $page_select.= "......... ";
1096 /* only show the page selector if we have more then one page */
1098 $this->tmpl->assign('page_selector', $page_select);
1102 $current_tags = $this->getCurrentTags();
1103 $extern_link = "index.php?mode=showpi";
1104 $rss_link = "index.php?mode=rss";
1105 if($current_tags != "") {
1106 $extern_link.= "&tags=". $current_tags;
1107 $rss_link.= "&tags=". $current_tags;
1109 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1110 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1111 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1114 $export_link = "index.php?mode=export";
1115 $slideshow_link = "index.php?mode=slideshow";
1117 $this->tmpl->assign('extern_link', $extern_link);
1118 $this->tmpl->assign('slideshow_link', $slideshow_link);
1119 $this->tmpl->assign('export_link', $export_link);
1120 $this->tmpl->assign('rss_link', $rss_link);
1121 $this->tmpl->assign('count', $count);
1122 $this->tmpl->assign('width', $this->cfg->thumb_width);
1123 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1124 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1125 $this->tmpl->assign('images', $images);
1126 $this->tmpl->assign('img_width', $img_width);
1127 $this->tmpl->assign('img_height', $img_height);
1128 $this->tmpl->assign('img_id', $img_id);
1129 $this->tmpl->assign('img_name', $img_name);
1130 $this->tmpl->assign('img_title', $img_title);
1131 $this->tmpl->assign('thumbs', $thumbs);
1133 $this->tmpl->show("photo_index.tpl");
1136 print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
1138 } // showPhotoIndex()
1141 * show credit template
1143 public function showCredits()
1145 $this->tmpl->assign('version', $this->cfg->version);
1146 $this->tmpl->assign('product', $this->cfg->product);
1147 $this->tmpl->assign('db_version', $this->dbver);
1148 $this->tmpl->show("credits.tpl");
1153 * create_thumbnails for the requested width
1155 * this function creates image thumbnails of $orig_image
1156 * stored as $thumb_image. It will check if the image is
1157 * in a supported format, if necessary rotate the image
1158 * (based on EXIF orientation meta headers) and re-sizing.
1160 public function create_thumbnail($orig_image, $thumb_image, $width)
1162 if(!file_exists($orig_image)) {
1166 $details = getimagesize($orig_image);
1168 /* check if original photo is a support image type */
1169 if(!$this->checkifImageSupported($details['mime']))
1172 $meta = $this->get_meta_informations($orig_image);
1178 switch($meta['Orientation']) {
1179 case 1: /* top, left */
1180 /* nothing to do */ break;
1181 case 2: /* top, right */
1182 $rotate = 0; $flip_hori = true; break;
1183 case 3: /* bottom, left */
1184 $rotate = 180; break;
1185 case 4: /* bottom, right */
1186 $flip_vert = true; break;
1187 case 5: /* left side, top */
1188 $rotate = 90; $flip_vert = true; break;
1189 case 6: /* right side, top */
1190 $rotate = 90; break;
1191 case 7: /* left side, bottom */
1192 $rotate = 270; $flip_vert = true; break;
1193 case 8: /* right side, bottom */
1194 $rotate = 270; break;
1197 $src_img = @imagecreatefromjpeg($orig_image);
1200 print "Can't load image from ". $orig_image ."\n";
1204 /* grabs the height and width */
1205 $cur_width = imagesx($src_img);
1206 $cur_height = imagesy($src_img);
1208 // If requested width is more then the actual image width,
1209 // do not generate a thumbnail, instead safe the original
1210 // as thumbnail but with lower quality. But if the image
1211 // is to heigh too, then we still have to resize it.
1212 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1213 $result = imagejpeg($src_img, $thumb_image, 75);
1214 imagedestroy($src_img);
1218 // If the image will be rotate because EXIF orientation said so
1219 // 'virtually rotate' the image for further calculations
1220 if($rotate == 90 || $rotate == 270) {
1222 $cur_width = $cur_height;
1226 /* calculates aspect ratio */
1227 $aspect_ratio = $cur_height / $cur_width;
1230 if($aspect_ratio < 1) {
1232 $new_h = abs($new_w * $aspect_ratio);
1234 /* 'virtually' rotate the image and calculate it's ratio */
1235 $tmp_w = $cur_height;
1236 $tmp_h = $cur_width;
1237 /* now get the ratio from the 'rotated' image */
1238 $tmp_ratio = $tmp_h/$tmp_w;
1239 /* now calculate the new dimensions */
1241 $tmp_h = abs($tmp_w * $tmp_ratio);
1243 // now that we know, how high they photo should be, if it
1244 // gets rotated, use this high to scale the image
1246 $new_w = abs($new_h / $aspect_ratio);
1248 // If the image will be rotate because EXIF orientation said so
1249 // now 'virtually rotate' back the image for the image manipulation
1250 if($rotate == 90 || $rotate == 270) {
1257 /* creates new image of that size */
1258 $dst_img = imagecreatetruecolor($new_w, $new_h);
1260 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1262 /* copies resized portion of original image into new image */
1263 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1265 /* needs the image to be flipped horizontal? */
1267 $this->_debug("(FLIP)");
1268 $dst_img = $this->flipImage($dst_img, 'hori');
1270 /* needs the image to be flipped vertical? */
1272 $this->_debug("(FLIP)");
1273 $dst_img = $this->flipImage($dst_img, 'vert');
1277 $this->_debug("(ROTATE)");
1278 $dst_img = $this->rotateImage($dst_img, $rotate);
1281 /* write down new generated file */
1282 $result = imagejpeg($dst_img, $thumb_image, 75);
1284 /* free your mind */
1285 imagedestroy($dst_img);
1286 imagedestroy($src_img);
1288 if($result === false) {
1289 print "Can't write thumbnail ". $thumb_image ."\n";
1295 } // create_thumbnail()
1298 * return all exif meta data from the file
1300 public function get_meta_informations($file)
1302 return exif_read_data($file);
1304 } // get_meta_informations()
1307 * create phpfspot own sqlite database
1309 * this function creates phpfspots own sqlite database
1310 * if it does not exist yet. this own is used to store
1311 * some necessary informations (md5 sum's, ...).
1313 public function check_config_table()
1315 // if the config table doesn't exist yet, create it
1316 if(!$this->cfg_db->db_check_table_exists("images")) {
1317 $this->cfg_db->db_exec("
1318 CREATE TABLE images (
1319 img_idx int primary key,
1325 } // check_config_table
1328 * Generates a thumbnail from photo idx
1330 * This function will generate JPEG thumbnails from provided F-Spot photo
1333 * 1. Check if all thumbnail generations (width) are already in place and
1335 * 2. Check if the md5sum of the original file has changed
1336 * 3. Generate the thumbnails if needed
1338 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1342 $resolutions = Array(
1343 $this->cfg->thumb_width,
1344 $this->cfg->photo_width,
1345 $this->cfg->mini_width,
1348 /* get details from F-Spot's database */
1349 $details = $this->get_photo_details($idx);
1351 /* calculate file MD5 sum */
1352 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1354 if(!file_exists($full_path)) {
1355 $this->_error("File ". $full_path ." does not exist\n");
1359 if(!is_readable($full_path)) {
1360 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1364 $file_md5 = md5_file($full_path);
1366 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1370 foreach($resolutions as $resolution) {
1372 $generate_it = false;
1374 $thumb_sub_path = substr($file_md5, 0, 2);
1375 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1377 if(!file_exists(dirname($thumb_path))) {
1378 mkdir(dirname($thumb_path), 0755);
1381 /* if the thumbnail file doesn't exist, create it */
1382 if(!file_exists($thumb_path)) {
1383 $generate_it = true;
1385 /* if the file hasn't changed there is no need to regen the thumb */
1386 elseif($file_md5 != $this->getMD5($idx) || $force) {
1387 $generate_it = true;
1390 if($generate_it || $overwrite) {
1392 $this->_debug(" ". $resolution ."px");
1393 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1401 $this->_debug(" already exist");
1404 /* set the new/changed MD5 sum for the current photo */
1406 $this->setMD5($idx, $file_md5);
1409 $this->_debug("\n");
1414 * returns stored md5 sum for a specific photo
1416 * this function queries the phpfspot database for a
1417 * stored MD5 checksum of the specified photo
1419 public function getMD5($idx)
1421 $result = $this->cfg_db->db_query("
1424 WHERE img_idx='". $idx ."'
1430 $img = $this->cfg_db->db_fetch_object($result);
1431 return $img['img_md5'];
1436 * set MD5 sum for the specific photo
1438 private function setMD5($idx, $md5)
1440 $result = $this->cfg_db->db_exec("
1441 REPLACE INTO images (img_idx, img_md5)
1442 VALUES ('". $idx ."', '". $md5 ."')
1448 * store current tag condition
1450 * this function stores the current tag condition
1451 * (AND or OR) in the users session variables
1453 public function setTagCondition($mode)
1455 $_SESSION['tag_condition'] = $mode;
1459 } // setTagCondition()
1462 * invoke tag & date search
1464 * this function will return all matching tags and store
1465 * them in the session variable selected_tags. furthermore
1466 * it also handles the date search.
1467 * getPhotoSelection() will then only return the matching
1470 public function startSearch($searchfor_tag, $from = 0, $to = 0)
1472 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1473 $from = $_POST['from'];
1475 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1479 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1480 $searchfor_tag = $_POST['for_tag'];
1483 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1484 $searchfor_name = $_POST['for_name'];
1489 $_SESSION['searchfor_tag'] = $searchfor_tag;
1490 $_SESSION['searchfor_name'] = $searchfor_name;
1493 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1495 unset($_SESSION['from_date']);
1498 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1500 unset($_SESSION['to_date']);
1502 if($searchfor_tag != "") {
1503 /* new search, reset the current selected tags */
1504 $_SESSION['selected_tags'] = Array();
1505 foreach($this->avail_tags as $tag) {
1506 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1507 array_push($_SESSION['selected_tags'], $tag);
1516 * updates sort order in session variable
1518 * this function is invoked by RPC and will sort the requested
1519 * sort order in the session variable.
1521 public function updateSortOrder($order)
1523 if(isset($this->sort_orders[$order])) {
1524 $_SESSION['sort_order'] = $order;
1528 return "unkown error";
1530 } // updateSortOrder()
1535 * this function rotates the image according the
1538 private function rotateImage($img, $degrees)
1540 if(function_exists("imagerotate")) {
1541 $img = imagerotate($img, $degrees, 0);
1543 function imagerotate($src_img, $angle)
1545 $src_x = imagesx($src_img);
1546 $src_y = imagesy($src_img);
1547 if ($angle == 180) {
1551 elseif ($src_x <= $src_y) {
1555 elseif ($src_x >= $src_y) {
1560 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1561 imagealphablending($rotate, false);
1566 for ($y = 0; $y < ($src_y); $y++) {
1567 for ($x = 0; $x < ($src_x); $x++) {
1568 $color = imagecolorat($src_img, $x, $y);
1569 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1575 for ($y = 0; $y < ($src_y); $y++) {
1576 for ($x = 0; $x < ($src_x); $x++) {
1577 $color = imagecolorat($src_img, $x, $y);
1578 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1584 for ($y = 0; $y < ($src_y); $y++) {
1585 for ($x = 0; $x < ($src_x); $x++) {
1586 $color = imagecolorat($src_img, $x, $y);
1587 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1601 $img = imagerotate($img, $degrees);
1610 * returns flipped image
1612 * this function will return an either horizontal or
1613 * vertical flipped truecolor image.
1615 private function flipImage($image, $mode)
1617 $w = imagesx($image);
1618 $h = imagesy($image);
1619 $flipped = imagecreatetruecolor($w, $h);
1623 for ($y = 0; $y < $h; $y++) {
1624 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1628 for ($x = 0; $x < $w; $x++) {
1629 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1639 * return all assigned tags for the specified photo
1641 private function get_photo_tags($idx)
1643 $result = $this->db->db_query("
1646 INNER JOIN photo_tags pt
1648 WHERE pt.photo_id='". $idx ."'
1653 while($row = $this->db->db_fetch_object($result))
1654 $tags[$row['id']] = $row['name'];
1658 } // get_photo_tags()
1661 * create on-the-fly images with text within
1663 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1665 if (strlen($color) != 6)
1668 $int = hexdec($color);
1669 $h = imagefontheight($font);
1670 $fw = imagefontwidth($font);
1671 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1672 $lines = count($txt);
1673 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1674 $bg = imagecolorallocate($im, 255, 255, 255);
1675 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1678 foreach ($txt as $text) {
1679 $x = (($w - ($fw * strlen($text))) / 2);
1680 imagestring($im, $font, $x, $y, $text, $color);
1681 $y += ($h + $space);
1684 Header("Content-type: image/png");
1687 } // showTextImage()
1690 * check if all requirements are met
1692 private function checkRequirements()
1694 if(!function_exists("imagecreatefromjpeg")) {
1695 print "PHP GD library extension is missing<br />\n";
1699 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1700 print "PHP SQLite3 library extension is missing<br />\n";
1704 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1705 ini_set('track_errors', 1);
1706 @include_once 'HTML/AJAX/Server.php';
1707 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1708 print "PEAR HTML_AJAX package is missing<br />\n";
1711 @include_once 'Calendar/Calendar.php';
1712 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1713 print "PEAR Calendar package is missing<br />\n";
1716 @include_once 'Console/Getopt.php';
1717 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1718 print "PEAR Console_Getopt package is missing<br />\n";
1721 ini_restore('track_errors');
1728 } // checkRequirements()
1730 private function _debug($text)
1732 if($this->fromcmd) {
1739 * check if specified MIME type is supported
1741 public function checkifImageSupported($mime)
1743 if(in_array($mime, Array("image/jpeg")))
1748 } // checkifImageSupported()
1750 public function _error($text)
1752 switch($this->cfg->logging) {
1755 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1756 print $text ."<br />\n";
1762 error_log($text, 3, $his->cfg->log_file);
1766 $this->runtime_error = true;
1771 * output calendard input fields
1773 private function get_calendar($mode)
1775 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1776 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1777 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1779 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1780 if(!isset($_SESSION[$mode .'_date']))
1781 $output.= " disabled=\"disabled\"";
1783 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1784 if(!isset($_SESSION[$mode .'_date']))
1785 $output.= " disabled=\"disabled\"";
1787 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1788 if(!isset($_SESSION[$mode .'_date']))
1789 $output.= " disabled=\"disabled\"";
1797 * output calendar matrix
1799 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1801 if (!isset($year)) $year = date('Y');
1802 if (!isset($month)) $month = date('m');
1803 if (!isset($day)) $day = date('d');
1808 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1809 require_once CALENDAR_ROOT.'Day.php';
1812 $month = new Calendar_Month_Weekdays($year,$month);
1815 $prevStamp = $month->prevMonth(true);
1816 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1817 $nextStamp = $month->nextMonth(true);
1818 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1820 $selectedDays = array (
1821 new Calendar_Day($year,$month,$day),
1822 new Calendar_Day($year,12,25),
1825 // Build the days in the month
1826 $month->build($selectedDays);
1828 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1829 $this->tmpl->assign('prev_month', $prev);
1830 $this->tmpl->assign('next_month', $next);
1832 while ( $day = $month->fetch() ) {
1834 if(!isset($matrix[$rows]))
1835 $matrix[$rows] = Array();
1839 $dayStamp = $day->thisDay(true);
1840 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1842 // isFirst() to find start of week
1843 if ( $day->isFirst() )
1846 if ( $day->isSelected() ) {
1847 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1848 } else if ( $day->isEmpty() ) {
1849 $string.= "<td> </td>\n";
1851 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1854 // isLast() to find end of week
1855 if ( $day->isLast() )
1856 $string.= "</tr>\n";
1858 $matrix[$rows][$cols] = $string;
1868 $this->tmpl->assign('matrix', $matrix);
1869 $this->tmpl->assign('rows', $rows);
1870 $this->tmpl->show("calendar.tpl");
1872 } // get_calendar_matrix()
1875 * output export page
1877 public function getExport($mode)
1879 $pictures = $this->getPhotoSelection();
1880 $current_tags = $this->getCurrentTags();
1882 foreach($pictures as $picture) {
1884 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1885 if($current_tags != "") {
1886 $orig_url.= "&tags=". $current_tags;
1888 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1889 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1892 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1897 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1898 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1902 // "[%pictureurl% %thumbnailurl%]"
1903 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1906 case 'MoinMoinList':
1907 // " * [%pictureurl% %thumbnailurl%]"
1908 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1919 public function getRSSFeed()
1921 Header("Content-type: text/xml; charset=utf-8");
1922 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1925 xmlns:media="http://search.yahoo.com/mrss/"
1926 xmlns:dc="http://purl.org/dc/elements/1.1/"
1929 <title>phpfspot</title>
1930 <description>phpfspot RSS feed</description>
1931 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1932 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1933 <generator>phpfspot</generator>
1936 $pictures = $this->getPhotoSelection();
1937 $current_tags = $this->getCurrentTags();
1939 foreach($pictures as $picture) {
1941 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1942 if($current_tags != "") {
1943 $orig_url.= "&tags=". $current_tags;
1945 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1946 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1949 $details = $this->get_photo_details($picture);
1951 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1952 $thumb_html = htmlspecialchars("
1953 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
1955 ". $details['description']);
1957 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1958 $meta = $this->get_meta_informations($orig_path);
1959 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
1963 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
1964 <link><?php print htmlspecialchars($orig_url); ?></link>
1965 <guid><?php print htmlspecialchars($orig_url); ?></guid>
1966 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
1968 <?php print $thumb_html; ?>
1970 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
1985 * return all selected tags as one string
1987 private function getCurrentTags()
1990 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
1991 foreach($_SESSION['selected_tags'] as $tag)
1992 $current_tags.= $tag .",";
1993 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1995 return $current_tags;
1997 } // getCurrentTags()
2000 * return the current photo
2002 public function getCurrentPhoto()
2004 if(isset($_SESSION['current_photo'])) {
2005 print $_SESSION['current_photo'];
2007 } // getCurrentPhoto()
2010 * tells the client browser what to do
2012 * this function is getting called via AJAX by the
2013 * client browsers. it will tell them what they have
2014 * to do next. This is necessary for directly jumping
2015 * into photo index or single photo view when the are
2016 * requested with specific URLs
2018 public function whatToDo()
2020 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2021 return "show_photo";
2023 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2024 return "showpi_tags";
2026 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2030 return "nothing special";
2035 * return the current process-user
2037 private function getuid()
2039 if($uid = posix_getuid()) {
2040 if($user = posix_getpwuid($uid)) {
2041 return $user['name'];
2050 * returns a select-dropdown box to select photo index sort parameters
2052 public function smarty_sort_select_list($params, &$smarty)
2056 foreach($this->sort_orders as $key => $value) {
2057 $output.= "<option value=\"". $key ."\"";
2058 if($key == $_SESSION['sort_order']) {
2059 $output.= " selected=\"selected\"";
2061 $output.= ">". $value ."</option>";
2066 } // smarty_sort_select_list()
2069 * returns the currently selected sort order
2071 private function get_sort_order()
2073 switch($_SESSION['sort_order']) {
2075 return " ORDER BY p.time ASC";
2078 return " ORDER BY p.time DESC";
2081 if($this->dbver < 9) {
2082 return " ORDER BY p.name ASC";
2085 return " ORDER BY basename(p.uri) ASC";
2089 if($this->dbver < 9) {
2090 return " ORDER BY p.name DESC";
2093 return " ORDER BY basename(p.uri) DESC";
2097 return " ORDER BY t.name ASC ,p.time ASC";
2100 return " ORDER BY t.name DESC ,p.time ASC";
2104 } // get_sort_order()
2107 * return the next to be shown slide show image
2109 * this function returns the URL of the next image
2110 * in the slideshow sequence.
2112 public function getNextSlideShowImage()
2114 $all_photos = $this->getPhotoSelection();
2116 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2117 $_SESSION['slideshow_img'] = 0;
2119 $_SESSION['slideshow_img']++;
2121 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2123 } // getNextSlideShowImage()
2126 * return the previous to be shown slide show image
2128 * this function returns the URL of the previous image
2129 * in the slideshow sequence.
2131 public function getPrevSlideShowImage()
2133 $all_photos = $this->getPhotoSelection();
2135 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2136 $_SESSION['slideshow_img'] = 0;
2138 $_SESSION['slideshow_img']--;
2140 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2142 } // getPrevSlideShowImage()
2144 public function resetSlideShow()
2146 if(isset($_SESSION['slideshow_img']))
2147 unset($_SESSION['slideshow_img']);
2149 } // resetSlideShow()
2154 * this function will get all photos from the fspot
2155 * database and randomly return ONE entry
2157 * saddly there is yet no sqlite3 function which returns
2158 * the bulk result in array, so we have to fill up our
2161 public function get_random_photo()
2165 $result = $this->db->db_query("
2170 while($row = $this->db->db_fetch_object($result)) {
2171 array_push($all, $row['id']);
2174 return $all[array_rand($all)];
2176 } // get_random_photo()
2179 * validates provided date
2181 * this function validates if the provided date
2182 * contains a valid date and will return true
2185 public function isValidDate($date_str)
2187 $timestamp = strtotime($date_str);
2189 if(is_numeric($timestamp))
2197 * timestamp to string conversion
2199 private function ts2str($timestamp)
2201 return strftime("%Y-%m-%d", $timestamp);
2204 private function extractTags($tags_str)
2206 $not_validated = split(',', $_GET['tags']);
2207 $validated = array();
2209 foreach($not_validated as $tag) {
2210 if(is_numeric($tag))
2211 array_push($validated, $tag);
2219 * returns the full path to a thumbnail
2221 public function get_thumb_path($width, $photo)
2223 $md5 = $this->getMD5($photo);
2224 $sub_path = substr($md5, 0, 2);
2225 return $this->cfg->thumb_path
2233 } // get_thumb_path()
2236 * returns server's virtual host name
2238 private function get_server_name()
2240 return $_SERVER['SERVER_NAME'];
2241 } // get_server_name()
2244 * returns type of webprotocol which is
2247 private function get_web_protocol()
2249 if(!isset($_SERVER['HTTPS']))
2253 } // get_web_protocol()
2256 * return url to this phpfspot installation
2258 private function get_phpfspot_url()
2260 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2261 } // get_phpfspot_url()
2264 * check file exists and is readable
2266 * returns true, if everything is ok, otherwise false
2267 * if $silent is not set, this function will output and
2270 private function check_readable($file, $silent = null)
2272 if(!file_exists($file)) {
2274 print "File \"". $file ."\" does not exist.\n";
2278 if(!is_readable($file)) {
2280 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2286 } // check_readable()
2289 * check if all needed indices are present
2291 * this function checks, if some needed indices are already
2292 * present, or if not, create them on the fly. they are
2293 * necessary to speed up some queries like that one look for
2294 * all tags, when show_tags is specified in the configuration.
2296 private function checkDbIndices()
2298 $result = $this->db->db_exec("
2299 CREATE INDEX IF NOT EXISTS
2306 } // checkDbIndices()
2309 * retrive F-Spot database version
2311 * this function will return the F-Spot database version number
2312 * It is stored within the sqlite3 database in the table meta
2314 public function getFspotDBVersion()
2316 if($result = $this->db->db_fetchSingleRow("
2317 SELECT data as version
2320 name LIKE 'F-Spot Database Version'
2322 return $result['version'];
2326 } // getFspotDBVersion()
2329 * parse the provided URI and will returned the
2332 public function parse_uri($uri, $mode)
2334 if(($components = parse_url($uri)) !== false) {
2338 return basename($components['path']);
2341 return dirname($components['path']);
2344 return $components['path'];
2354 * validate config options
2356 * this function checks if all necessary configuration options are
2357 * specified and set.
2359 private function check_config_options()
2361 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2362 $this->_error("Please set \$page_title in phpfspot_cfg");
2364 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2365 $this->_error("Please set \$base_path in phpfspot_cfg");
2367 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2368 $this->_error("Please set \$web_path in phpfspot_cfg");
2370 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2371 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2373 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2374 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2376 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2377 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2379 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2380 $this->_error("Please set \$db_access in phpfspot_cfg");
2382 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2383 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2385 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2386 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2388 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2389 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2391 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2392 $this->_error("Please set \$photo_width in phpfspot_cfg");
2394 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2395 $this->_error("Please set \$mini_width in phpfspot_cfg");
2397 if(!isset($this->cfg->thumbs_per_page))
2398 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2400 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2401 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2403 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2404 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2406 if(!isset($this->cfg->hide_tags))
2407 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2409 if(!isset($this->cfg->theme_name))
2410 $this->_error("Please set \$theme_name in phpfspot_cfg");
2412 if(!isset($this->cfg->logging))
2413 $this->_error("Please set \$logging in phpfspot_cfg");
2415 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2417 if(!isset($this->cfg->log_file))
2418 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2420 if(!is_writeable($this->cfg->log_file))
2421 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2425 /* check for pending slash on web_path */
2426 if(!preg_match("/\/$/", $this->web_path))
2427 $this->web_path.= "/";
2429 return $this->runtime_error;
2431 } // check_config_options()
2434 * cleanup phpfspot own database
2436 * When photos are getting delete from F-Spot, there will remain
2437 * remain some residues in phpfspot own database. This function
2438 * will try to wipe them out.
2440 public function cleanup_phpfspot_db()
2442 $to_delete = Array();
2444 $result = $this->cfg_db->db_query("
2447 ORDER BY img_idx ASC
2450 while($row = $this->cfg_db->db_fetch_object($result)) {
2451 if(!$this->db->db_fetchSingleRow("
2454 WHERE id='". $row['img_idx'] ."'")) {
2456 array_push($to_delete, $row['img_idx'], ',');
2460 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2462 $this->cfg_db->db_exec("
2464 WHERE img_idx IN (". implode($to_delete) .")
2467 } // cleanup_phpfspot_db()