3 /***************************************************************************
5 * Copyright (c) by Andreas Unterkircher, unki@netshadow.at
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 ***************************************************************************/
24 require_once "phpfspot_cfg.php";
25 require_once "phpfspot_db.php";
36 private $runtime_error = false;
42 * this function will be called on class construct
43 * and will check requirements, loads configuration,
44 * open databases and start the user session
46 public function __construct()
48 $this->cfg = new PHPFSPOT_CFG;
50 /* verify config settings */
51 if($this->check_config_options()) {
55 /* set application name and version information */
56 $this->cfg->product = "phpfspot";
57 $this->cfg->version = "1.4";
59 $this->sort_orders= array(
60 'date_asc' => 'Date ↑',
61 'date_desc' => 'Date ↓',
62 'name_asc' => 'Name ↑',
63 'name_desc' => 'Name ↓',
64 'tags_asc' => 'Tags ↑',
65 'tags_desc' => 'Tags ↓',
68 /* Check necessary requirements */
69 if(!$this->checkRequirements()) {
73 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
74 if(!is_writeable($this->cfg->fspot_db)) {
75 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
79 $this->dbver = $this->getFspotDBVersion();
81 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
82 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
86 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
87 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
91 if(!is_writeable($this->cfg->thumb_path)) {
92 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
96 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
97 if(!is_writeable($this->cfg->phpfspot_db)) {
98 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
102 $this->check_config_table();
104 /* include Smarty template engine */
105 if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
108 require $this->cfg->smarty_path .'/libs/Smarty.class.php';
109 /* overload Smarty class if our own template handler */
110 require_once "phpfspot_tmpl.php";
111 $this->tmpl = new PHPFSPOT_TMPL($this);
113 /* check if all necessary indices exist */
114 $this->checkDbIndices();
116 /* if session is not yet started, do it now */
117 if(session_id() == "")
120 if(!isset($_SESSION['tag_condition']))
121 $_SESSION['tag_condition'] = 'or';
123 if(!isset($_SESSION['sort_order']))
124 $_SESSION['sort_order'] = 'date_desc';
126 if(!isset($_SESSION['searchfor_tag']))
127 $_SESSION['searchfor_tag'] = '';
129 // if begin_with is still set but thumbs_per_page is now 0, unset it
130 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
131 unset($_SESSION['begin_with']);
135 public function __destruct()
141 * show - generate html output
143 * this function can be called after the constructor has
144 * prepared everyhing. it will load the index.tpl smarty
145 * template. if necessary it will registere pre-selects
146 * (photo index, photo, tag search, date search) into
149 public function show()
151 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
152 $this->tmpl->assign('page_title', $this->cfg->page_title);
153 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
154 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
156 if(isset($_GET['mode'])) {
158 $_SESSION['start_action'] = $_GET['mode'];
160 switch($_GET['mode']) {
162 if(isset($_GET['tags'])) {
163 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
165 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
166 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
168 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
169 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
173 if(isset($_GET['tags'])) {
174 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
175 $_SESSION['start_action'] = 'showp';
177 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
178 $_SESSION['current_photo'] = $_GET['id'];
179 $_SESSION['start_action'] = 'showp';
181 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
182 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
184 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
185 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
189 $this->tmpl->show("export.tpl");
193 $this->tmpl->show("slideshow.tpl");
197 if(isset($_GET['tags'])) {
198 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
200 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
201 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
203 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
204 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
212 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
213 $this->tmpl->assign('date_search_enabled', true);
215 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
216 $this->tmpl->assign('from_date', $this->get_calendar('from'));
217 $this->tmpl->assign('to_date', $this->get_calendar('to'));
218 $this->tmpl->assign('content_page', 'welcome.tpl');
219 $this->tmpl->show("index.tpl");
224 * get_tags - grab all tags of f-spot's database
226 * this function will get all available tags from
227 * the f-spot database and store them within two
228 * arrays within this class for later usage. in
229 * fact, if the user requests (hide_tags) it will
230 * opt-out some of them.
232 * this function is getting called once by show()
234 private function get_tags()
236 $this->avail_tags = Array();
239 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
242 DISTINCT t1.id as id, t1.name as name
245 INNER JOIN photo_tags
246 pt2 ON pt1.photo_id=pt2.photo_id
252 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
254 t1.sort_priority ASC";
256 $result = $this->db->db_query($query_str);
260 $result = $this->db->db_query("
263 ORDER BY sort_priority ASC
267 while($row = $this->db->db_fetch_object($result)) {
269 $tag_id = $row['id'];
270 $tag_name = $row['name'];
272 /* if the user has specified to ignore this tag in phpfspot's
273 configuration, ignore it here so it does not get added to
276 if(in_array($row['name'], $this->cfg->hide_tags))
279 /* if you include the following if-clause and the user has specified
280 to only show certain tags which are specified in phpfspot's
281 configuration, ignore all others so they will not be added to the
283 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
284 !in_array($row['name'], $this->cfg->show_tags))
288 $this->tags[$tag_id] = $tag_name;
289 $this->avail_tags[$count] = $tag_id;
297 * extract all photo details
299 * retrieve all available details from f-spot's
300 * database and return them as object
302 public function get_photo_details($idx)
304 if($this->dbver < 9) {
306 SELECT p.id, p.name, p.time, p.directory_path, p.description
312 SELECT p.id, p.uri, p.time, p.description
317 /* if show_tags is set, only return details for photos which
318 are specified to be shown
320 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
322 INNER JOIN photo_tags pt
326 WHERE p.id='". $idx ."'
327 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
331 WHERE p.id='". $idx ."'
335 if($result = $this->db->db_query($query_str)) {
337 $row = $this->db->db_fetch_object($result);
339 if($this->dbver < 9) {
340 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
349 } // get_photo_details
352 * returns aligned photo names
354 * this function returns aligned (length) names for
355 * an specific photo. If the length of the name exceeds
356 * $limit the name will be shrinked (...)
358 public function getPhotoName($idx, $limit = 0)
360 if($details = $this->get_photo_details($idx)) {
361 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
362 $name = $this->shrink_text($long_name, $limit);
372 * shrink text according provided limit
374 * If the length of the name exceeds $limit the
375 * text will be shortend and some content in between
376 * will be replaced with "..."
378 private function shrink_text($text, $limit)
380 if($limit != 0 && strlen($text) > $limit) {
381 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
389 * translate f-spoth photo path
391 * as the full-qualified path recorded in the f-spot database
392 * is usally not the same as on the webserver, this function
393 * will replace the path with that one specified in the cfg
395 public function translate_path($path, $width = 0)
397 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
402 * control HTML ouput for a single photo
404 * this function provides all the necessary information
405 * for the single photo template.
407 public function showPhoto($photo)
409 /* get all photos from the current photo selection */
410 $all_photos = $this->getPhotoSelection();
411 $count = count($all_photos);
413 for($i = 0; $i < $count; $i++) {
415 // $get_next will be set, when the photo which has to
416 // be displayed has been found - this means that the
417 // next available is in fact the NEXT image (for the
419 if(isset($get_next)) {
420 $next_img = $all_photos[$i];
424 /* the next photo is our NEXT photo */
425 if($all_photos[$i] == $photo) {
429 $previous_img = $all_photos[$i];
432 if($photo == $all_photos[$i]) {
437 $details = $this->get_photo_details($photo);
444 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
445 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
447 if(!file_exists($orig_path)) {
448 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
452 if(!is_readable($orig_path)) {
453 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
457 /* If the thumbnail doesn't exist yet, try to create it */
458 if(!file_exists($thumb_path)) {
459 $this->gen_thumb($photo, true);
460 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
463 /* get EXIF information if JPEG */
464 if($details['mime'] == "image/jpeg") {
465 $meta = $this->get_meta_informations($orig_path);
468 /* If EXIF data are available, use them */
469 if(isset($meta['ExifImageWidth'])) {
470 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
472 $info = getimagesize($orig_path);
473 $meta_res = $info[0] ."x". $info[1];
476 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
477 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
478 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
480 $extern_link = "index.php?mode=showp&id=". $photo;
481 $current_tags = $this->getCurrentTags();
482 if($current_tags != "") {
483 $extern_link.= "&tags=". $current_tags;
485 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
486 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
489 $this->tmpl->assign('extern_link', $extern_link);
491 if(!file_exists($thumb_path)) {
492 $this->_error("Can't open file ". $thumb_path ."\n");
496 $info = getimagesize($thumb_path);
498 $this->tmpl->assign('description', $details['description']);
499 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
501 $this->tmpl->assign('width', $info[0]);
502 $this->tmpl->assign('height', $info[1]);
503 $this->tmpl->assign('ExifMadeOn', $meta_date);
504 $this->tmpl->assign('ExifMadeWith', $meta_make);
505 $this->tmpl->assign('ExifOrigResolution', $meta_res);
506 $this->tmpl->assign('ExifFileSize', $meta_size);
508 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
509 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
510 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
512 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
513 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
514 $this->tmpl->assign('current_img', $photo);
517 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
518 $this->tmpl->assign('prev_img', $previous_img);
522 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
523 $this->tmpl->assign('next_img', $next_img);
525 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
526 $this->tmpl->assign('photo_number', $i);
527 $this->tmpl->assign('photo_count', count($all_photos));
529 $this->tmpl->show("single_photo.tpl");
534 * all available tags and tag cloud
536 * this function outputs all available tags (time ordered)
537 * and in addition output them as tag cloud (tags which have
538 * many photos will appears more then others)
540 public function getAvailableTags()
542 /* retrive tags from database */
547 $result = $this->db->db_query("
548 SELECT tag_id as id, count(tag_id) as quantity
558 while($row = $this->db->db_fetch_object($result)) {
559 $tags[$row['id']] = $row['quantity'];
562 // change these font sizes if you will
563 $max_size = 125; // max font size in %
564 $min_size = 75; // min font size in %
566 // get the largest and smallest array values
567 $max_qty = max(array_values($tags));
568 $min_qty = min(array_values($tags));
570 // find the range of values
571 $spread = $max_qty - $min_qty;
572 if (0 == $spread) { // we don't want to divide by zero
576 // determine the font-size increment
577 // this is the increase per tag quantity (times used)
578 $step = ($max_size - $min_size)/($spread);
580 // loop through our tag array
581 foreach ($tags as $key => $value) {
583 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
586 // calculate CSS font-size
587 // find the $value in excess of $min_qty
588 // multiply by the font-size increment ($size)
589 // and add the $min_size set above
590 $size = $min_size + (($value - $min_qty) * $step);
591 // uncomment if you want sizes in whole %:
594 if(isset($this->tags[$key])) {
595 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
600 $output = substr($output, 0, strlen($output)-2);
603 } // getAvailableTags()
606 * output all selected tags
608 * this function output all tags which have been selected
609 * by the user. the selected tags are stored in the
610 * session-variable $_SESSION['selected_tags']
612 public function getSelectedTags()
614 /* retrive tags from database */
619 foreach($this->avail_tags as $tag)
621 // return all selected tags
622 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
623 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
628 $output = substr($output, 0, strlen($output)-2);
632 return "no tags selected";
635 } // getSelectedTags()
638 * add tag to users session variable
640 * this function will add the specified to users current
641 * tag selection. if a date search has been made before
642 * it will be now cleared
644 public function addTag($tag)
646 if(!isset($_SESSION['selected_tags']))
647 $_SESSION['selected_tags'] = Array();
649 if(isset($_SESSION['searchfor_tag']))
650 unset($_SESSION['searchfor_tag']);
652 if(!in_array($tag, $_SESSION['selected_tags']))
653 array_push($_SESSION['selected_tags'], $tag);
661 * remove tag to users session variable
663 * this function removes the specified tag from
664 * users current tag selection
666 public function delTag($tag)
668 if(isset($_SESSION['searchfor_tag']))
669 unset($_SESSION['searchfor_tag']);
671 if(isset($_SESSION['selected_tags'])) {
672 $key = array_search($tag, $_SESSION['selected_tags']);
673 unset($_SESSION['selected_tags'][$key]);
674 sort($_SESSION['selected_tags']);
682 * reset tag selection
684 * if there is any tag selection, it will be
687 public function resetTags()
689 if(isset($_SESSION['selected_tags']))
690 unset($_SESSION['selected_tags']);
695 * returns the value for the autocomplet tag-search
697 public function get_xml_tag_list()
699 if(!isset($_GET['search']) || !is_string($_GET['search']))
700 $_GET['search'] = '';
705 /* retrive tags from database */
708 $matched_tags = Array();
710 header("Content-Type: text/xml");
712 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
713 $string.= "<results>\n";
715 foreach($this->avail_tags as $tag)
717 if(!empty($_GET['search']) &&
718 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
719 count($matched_tags) < $length) {
721 $count = $this->get_num_photos($tag);
724 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
727 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
733 /* if we have collected enough items, break out */
734 if(count($matched_tags) >= $length)
738 $string.= "</results>\n";
742 } // get_xml_tag_list()
748 * if a specific photo was requested (external link)
749 * unset the session variable now
751 public function resetPhotoView()
753 if(isset($_SESSION['current_photo']))
754 unset($_SESSION['current_photo']);
756 } // resetPhotoView();
761 * if any tag search has taken place, reset it now
763 public function resetTagSearch()
765 if(isset($_SESSION['searchfor_tag']))
766 unset($_SESSION['searchfor_tag']);
768 } // resetTagSearch()
773 * if any name search has taken place, reset it now
775 public function resetNameSearch()
777 if(isset($_SESSION['searchfor_name']))
778 unset($_SESSION['searchfor_name']);
780 } // resetNameSearch()
785 * if any date search has taken place, reset
788 public function resetDateSearch()
790 if(isset($_SESSION['from_date']))
791 unset($_SESSION['from_date']);
792 if(isset($_SESSION['to_date']))
793 unset($_SESSION['to_date']);
795 } // resetDateSearch();
798 * return all photo according selection
800 * this function returns all photos based on
801 * the tag-selection, tag- or date-search.
802 * the tag-search also has to take care of AND
803 * and OR conjunctions
805 public function getPhotoSelection()
807 $matched_photos = Array();
808 $additional_where_cond = "";
810 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
811 $from_date = $_SESSION['from_date'];
812 $to_date = $_SESSION['to_date'];
813 $additional_where_cond.= "
814 p.time>='". $from_date ."'
816 p.time<='". $to_date ."'
820 if(isset($_SESSION['searchfor_name'])) {
821 if($this->dbver < 9) {
822 $additional_where_cond.= "
824 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
826 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
831 $additional_where_cond.= "
833 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
835 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
841 if(isset($_SESSION['sort_order'])) {
842 $order_str = $this->get_sort_order();
845 /* return a search result */
846 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
848 SELECT DISTINCT pt1.photo_id
850 INNER JOIN photo_tags pt2
851 ON pt1.photo_id=pt2.photo_id
858 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
860 if(isset($additional_where_cond) && !empty($additional_where_cond))
861 $query_str.= "AND ". $additional_where_cond ." ";
863 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
864 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
867 if(isset($order_str))
868 $query_str.= $order_str;
870 $result = $this->db->db_query($query_str);
871 while($row = $this->db->db_fetch_object($result)) {
872 array_push($matched_photos, $row['photo_id']);
874 return $matched_photos;
877 /* return according the selected tags */
878 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
880 foreach($_SESSION['selected_tags'] as $tag)
881 $selected.= $tag .",";
882 $selected = substr($selected, 0, strlen($selected)-1);
884 /* photo has to match at least on of the selected tags */
885 if($_SESSION['tag_condition'] == 'or') {
887 SELECT DISTINCT pt1.photo_id
889 INNER JOIN photo_tags pt2
890 ON pt1.photo_id=pt2.photo_id
895 WHERE pt1.tag_id IN (". $selected .")
897 if(isset($additional_where_cond) && !empty($additional_where_cond))
898 $query_str.= "AND ". $additional_where_cond ." ";
900 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
901 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
904 if(isset($order_str))
905 $query_str.= $order_str;
907 /* photo has to match all selected tags */
908 elseif($_SESSION['tag_condition'] == 'and') {
910 if(count($_SESSION['selected_tags']) >= 32) {
911 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
912 print "evaluate your tag selection. Please remove some tags from your selection.\n";
916 /* Join together a table looking like
918 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
920 so the query can quickly return all images matching the
921 selected tags in an AND condition
926 SELECT DISTINCT pt1.photo_id
930 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
937 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
939 INNER JOIN photo_tags pt". ($i+2) ."
940 ON pt1.photo_id=pt". ($i+2) .".photo_id
947 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
948 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
950 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
953 if(isset($additional_where_cond) && !empty($additional_where_cond))
954 $query_str.= "AND ". $additional_where_cond;
956 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
957 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
960 if(isset($order_str))
961 $query_str.= $order_str;
965 $result = $this->db->db_query($query_str);
966 while($row = $this->db->db_fetch_object($result)) {
967 array_push($matched_photos, $row['photo_id']);
969 return $matched_photos;
972 /* return all available photos */
976 LEFT JOIN photo_tags pt
982 if(isset($additional_where_cond) && !empty($additional_where_cond))
983 $query_str.= "WHERE ". $additional_where_cond ." ";
985 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
986 if(isset($additional_where_cond) && !empty($additional_where_cond))
987 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
989 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
992 if(isset($order_str))
993 $query_str.= $order_str;
995 $result = $this->db->db_query($query_str);
996 while($row = $this->db->db_fetch_object($result)) {
997 array_push($matched_photos, $row['id']);
999 return $matched_photos;
1001 } // getPhotoSelection()
1004 * control HTML ouput for photo index
1006 * this function provides all the necessary information
1007 * for the photo index template.
1009 public function showPhotoIndex()
1011 $photos = $this->getPhotoSelection();
1013 $count = count($photos);
1015 /* if all thumbnails should be shown on one page */
1016 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1020 /* thumbnails should be splitted up in several pages */
1021 elseif($this->cfg->thumbs_per_page > 0) {
1023 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1027 $begin_with = $_SESSION['begin_with'];
1030 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1034 $images[$thumbs] = Array();
1035 $img_height[$thumbs] = Array();
1036 $img_width[$thumbs] = Array();
1037 $img_id[$thumbs] = Array();
1038 $img_name[$thumbs] = Array();
1039 $img_fullname[$thumbs] = Array();
1040 $img_title = Array();
1042 for($i = $begin_with; $i < $end_with; $i++) {
1044 if(isset($photos[$i])) {
1046 $images[$thumbs] = $photos[$i];
1047 $img_id[$thumbs] = $i;
1048 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1049 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1050 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1052 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1054 if(file_exists($thumb_path)) {
1055 $info = getimagesize($thumb_path);
1056 $img_width[$thumbs] = $info[0];
1057 $img_height[$thumbs] = $info[1];
1063 // +1 for for smarty's selection iteration
1066 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1067 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1069 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1070 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1071 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1074 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1075 $this->tmpl->assign('tag_result', 1);
1078 /* do we have to display the page selector ? */
1079 if($this->cfg->thumbs_per_page != 0) {
1083 /* calculate the page switchers */
1084 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1085 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1087 if($begin_with != 0)
1088 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1089 if($end_with < $count)
1090 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1092 $photo_per_page = $this->cfg->thumbs_per_page;
1093 $last_page = ceil($count / $photo_per_page);
1095 /* get the current selected page */
1096 if($begin_with == 0) {
1100 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1107 for($i = 1; $i <= $last_page; $i++) {
1109 if($current_page == $i)
1110 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1111 elseif($current_page-1 == $i || $current_page+1 == $i)
1112 $style = "style=\"font-size: 105%;\"";
1113 elseif(($current_page-5 >= $i) && ($i != 1) ||
1114 ($current_page+5 <= $i) && ($i != $last_page))
1115 $style = "style=\"font-size: 75%;\"";
1119 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1122 $select.= ">". $i ."</a> ";
1124 // until 9 pages we show the selector from 1-9
1125 if($last_page <= 9) {
1126 $page_select.= $select;
1129 if($i == 1 /* first page */ ||
1130 $i == $last_page /* last page */ ||
1131 $i == $current_page /* current page */ ||
1132 $i == ceil($last_page * 0.25) /* first quater */ ||
1133 $i == ceil($last_page * 0.5) /* half */ ||
1134 $i == ceil($last_page * 0.75) /* third quater */ ||
1135 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1136 (in_array($i, array($last_page, $last_page-1, $last_page-2, $last_page-3, $last_page-4, $last_page-5)) && $current_page >= $last_page-4) /* the last 6 */ ||
1137 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1138 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1140 $page_select.= $select;
1148 $page_select.= "......... ";
1153 /* only show the page selector if we have more then one page */
1155 $this->tmpl->assign('page_selector', $page_select);
1159 $current_tags = $this->getCurrentTags();
1160 $extern_link = "index.php?mode=showpi";
1161 $rss_link = "index.php?mode=rss";
1162 if($current_tags != "") {
1163 $extern_link.= "&tags=". $current_tags;
1164 $rss_link.= "&tags=". $current_tags;
1166 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1167 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1168 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1171 $export_link = "index.php?mode=export";
1172 $slideshow_link = "index.php?mode=slideshow";
1174 $this->tmpl->assign('extern_link', $extern_link);
1175 $this->tmpl->assign('slideshow_link', $slideshow_link);
1176 $this->tmpl->assign('export_link', $export_link);
1177 $this->tmpl->assign('rss_link', $rss_link);
1178 $this->tmpl->assign('count', $count);
1179 $this->tmpl->assign('width', $this->cfg->thumb_width);
1180 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1181 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1182 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1183 $this->tmpl->assign('images', $images);
1184 $this->tmpl->assign('img_width', $img_width);
1185 $this->tmpl->assign('img_height', $img_height);
1186 $this->tmpl->assign('img_id', $img_id);
1187 $this->tmpl->assign('img_name', $img_name);
1188 $this->tmpl->assign('img_fullname', $img_fullname);
1189 $this->tmpl->assign('img_title', $img_title);
1190 $this->tmpl->assign('thumbs', $thumbs);
1192 $this->tmpl->show("photo_index.tpl");
1194 /* if we are returning to photo index from an photo-view,
1195 scroll the window to the last shown photo-thumbnail.
1196 after this, unset the last_photo session variable.
1198 if(isset($_SESSION['last_photo'])) {
1199 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1200 unset($_SESSION['last_photo']);
1203 } // showPhotoIndex()
1206 * show credit template
1208 public function showCredits()
1210 $this->tmpl->assign('version', $this->cfg->version);
1211 $this->tmpl->assign('product', $this->cfg->product);
1212 $this->tmpl->assign('db_version', $this->dbver);
1213 $this->tmpl->show("credits.tpl");
1218 * create_thumbnails for the requested width
1220 * this function creates image thumbnails of $orig_image
1221 * stored as $thumb_image. It will check if the image is
1222 * in a supported format, if necessary rotate the image
1223 * (based on EXIF orientation meta headers) and re-sizing.
1225 public function create_thumbnail($orig_image, $thumb_image, $width)
1227 if(!file_exists($orig_image)) {
1231 $details = getimagesize($orig_image);
1233 /* check if original photo is a support image type */
1234 if(!$this->checkifImageSupported($details['mime']))
1237 switch($details['mime']) {
1241 $meta = $this->get_meta_informations($orig_image);
1247 switch($meta['Orientation']) {
1248 case 1: /* top, left */
1249 /* nothing to do */ break;
1250 case 2: /* top, right */
1251 $rotate = 0; $flip_hori = true; break;
1252 case 3: /* bottom, left */
1253 $rotate = 180; break;
1254 case 4: /* bottom, right */
1255 $flip_vert = true; break;
1256 case 5: /* left side, top */
1257 $rotate = 90; $flip_vert = true; break;
1258 case 6: /* right side, top */
1259 $rotate = 90; break;
1260 case 7: /* left side, bottom */
1261 $rotate = 270; $flip_vert = true; break;
1262 case 8: /* right side, bottom */
1263 $rotate = 270; break;
1266 $src_img = @imagecreatefromjpeg($orig_image);
1272 $src_img = @imagecreatefrompng($orig_image);
1278 $src_img = new Imagick($orig_image);
1279 print_r($src_img->queryFormats());
1281 $handler = "imagick";
1292 if(!isset($src_img) || empty($src_img)) {
1293 print "Can't load image from ". $orig_image ."\n";
1297 /* grabs the height and width */
1298 $cur_width = imagesx($src_img);
1299 $cur_height = imagesy($src_img);
1301 // If requested width is more then the actual image width,
1302 // do not generate a thumbnail, instead safe the original
1303 // as thumbnail but with lower quality. But if the image
1304 // is to heigh too, then we still have to resize it.
1305 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1306 $result = imagejpeg($src_img, $thumb_image, 75);
1307 imagedestroy($src_img);
1311 // If the image will be rotate because EXIF orientation said so
1312 // 'virtually rotate' the image for further calculations
1313 if($rotate == 90 || $rotate == 270) {
1315 $cur_width = $cur_height;
1319 /* calculates aspect ratio */
1320 $aspect_ratio = $cur_height / $cur_width;
1323 if($aspect_ratio < 1) {
1325 $new_h = abs($new_w * $aspect_ratio);
1327 /* 'virtually' rotate the image and calculate it's ratio */
1328 $tmp_w = $cur_height;
1329 $tmp_h = $cur_width;
1330 /* now get the ratio from the 'rotated' image */
1331 $tmp_ratio = $tmp_h/$tmp_w;
1332 /* now calculate the new dimensions */
1334 $tmp_h = abs($tmp_w * $tmp_ratio);
1336 // now that we know, how high they photo should be, if it
1337 // gets rotated, use this high to scale the image
1339 $new_w = abs($new_h / $aspect_ratio);
1341 // If the image will be rotate because EXIF orientation said so
1342 // now 'virtually rotate' back the image for the image manipulation
1343 if($rotate == 90 || $rotate == 270) {
1350 /* creates new image of that size */
1351 $dst_img = imagecreatetruecolor($new_w, $new_h);
1353 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1355 /* copies resized portion of original image into new image */
1356 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1358 /* needs the image to be flipped horizontal? */
1360 $this->_debug("(FLIP)");
1361 $dst_img = $this->flipImage($dst_img, 'hori');
1363 /* needs the image to be flipped vertical? */
1365 $this->_debug("(FLIP)");
1366 $dst_img = $this->flipImage($dst_img, 'vert');
1370 $this->_debug("(ROTATE)");
1371 $dst_img = $this->rotateImage($dst_img, $rotate);
1374 /* write down new generated file */
1375 $result = imagejpeg($dst_img, $thumb_image, 75);
1377 /* free your mind */
1378 imagedestroy($dst_img);
1379 imagedestroy($src_img);
1381 if($result === false) {
1382 print "Can't write thumbnail ". $thumb_image ."\n";
1396 } // create_thumbnail()
1399 * return all exif meta data from the file
1401 public function get_meta_informations($file)
1403 return exif_read_data($file);
1405 } // get_meta_informations()
1408 * create phpfspot own sqlite database
1410 * this function creates phpfspots own sqlite database
1411 * if it does not exist yet. this own is used to store
1412 * some necessary informations (md5 sum's, ...).
1414 public function check_config_table()
1416 // if the config table doesn't exist yet, create it
1417 if(!$this->cfg_db->db_check_table_exists("images")) {
1418 $this->cfg_db->db_exec("
1419 CREATE TABLE images (
1420 img_idx int primary key,
1426 } // check_config_table
1429 * Generates a thumbnail from photo idx
1431 * This function will generate JPEG thumbnails from provided F-Spot photo
1434 * 1. Check if all thumbnail generations (width) are already in place and
1436 * 2. Check if the md5sum of the original file has changed
1437 * 3. Generate the thumbnails if needed
1439 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1443 $resolutions = Array(
1444 $this->cfg->thumb_width,
1445 $this->cfg->photo_width,
1446 $this->cfg->mini_width,
1449 /* get details from F-Spot's database */
1450 $details = $this->get_photo_details($idx);
1452 /* calculate file MD5 sum */
1453 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1455 if(!file_exists($full_path)) {
1456 $this->_error("File ". $full_path ." does not exist\n");
1460 if(!is_readable($full_path)) {
1461 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1465 $file_md5 = md5_file($full_path);
1467 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1471 foreach($resolutions as $resolution) {
1473 $generate_it = false;
1475 $thumb_sub_path = substr($file_md5, 0, 2);
1476 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1478 /* if thumbnail-subdirectory does not exist yet, create it */
1479 if(!file_exists(dirname($thumb_path))) {
1480 mkdir(dirname($thumb_path), 0755);
1483 /* if the thumbnail file doesn't exist, create it */
1484 if(!file_exists($thumb_path)) {
1485 $generate_it = true;
1487 /* if the file hasn't changed there is no need to regen the thumb */
1488 elseif($file_md5 != $this->getMD5($idx) || $force) {
1489 $generate_it = true;
1492 if($generate_it || $overwrite) {
1494 $this->_debug(" ". $resolution ."px");
1495 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1503 $this->_debug(" already exist");
1506 /* set the new/changed MD5 sum for the current photo */
1508 $this->setMD5($idx, $file_md5);
1511 $this->_debug("\n");
1516 * returns stored md5 sum for a specific photo
1518 * this function queries the phpfspot database for a
1519 * stored MD5 checksum of the specified photo
1521 public function getMD5($idx)
1523 $result = $this->cfg_db->db_query("
1526 WHERE img_idx='". $idx ."'
1532 $img = $this->cfg_db->db_fetch_object($result);
1533 return $img['img_md5'];
1538 * set MD5 sum for the specific photo
1540 private function setMD5($idx, $md5)
1542 $result = $this->cfg_db->db_exec("
1543 REPLACE INTO images (img_idx, img_md5)
1544 VALUES ('". $idx ."', '". $md5 ."')
1550 * store current tag condition
1552 * this function stores the current tag condition
1553 * (AND or OR) in the users session variables
1555 public function setTagCondition($mode)
1557 $_SESSION['tag_condition'] = $mode;
1561 } // setTagCondition()
1564 * invoke tag & date search
1566 * this function will return all matching tags and store
1567 * them in the session variable selected_tags. furthermore
1568 * it also handles the date search.
1569 * getPhotoSelection() will then only return the matching
1572 public function startSearch()
1574 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1575 $from = $_POST['from'];
1577 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1581 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1582 $searchfor_tag = $_POST['for_tag'];
1583 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1586 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1587 $searchfor_name = $_POST['for_name'];
1588 $_SESSION['searchfor_name'] = $_POST['for_name'];
1593 if(isset($from) && !empty($from))
1594 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1596 unset($_SESSION['from_date']);
1598 if(isset($to) && !empty($to))
1599 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1601 unset($_SESSION['to_date']);
1603 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1604 /* new search, reset the current selected tags */
1605 $_SESSION['selected_tags'] = Array();
1606 foreach($this->avail_tags as $tag) {
1607 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1608 array_push($_SESSION['selected_tags'], $tag);
1617 * updates sort order in session variable
1619 * this function is invoked by RPC and will sort the requested
1620 * sort order in the session variable.
1622 public function updateSortOrder($order)
1624 if(isset($this->sort_orders[$order])) {
1625 $_SESSION['sort_order'] = $order;
1629 return "unkown error";
1631 } // updateSortOrder()
1636 * this function rotates the image according the
1639 private function rotateImage($img, $degrees)
1641 if(function_exists("imagerotate")) {
1642 $img = imagerotate($img, $degrees, 0);
1644 function imagerotate($src_img, $angle)
1646 $src_x = imagesx($src_img);
1647 $src_y = imagesy($src_img);
1648 if ($angle == 180) {
1652 elseif ($src_x <= $src_y) {
1656 elseif ($src_x >= $src_y) {
1661 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1662 imagealphablending($rotate, false);
1667 for ($y = 0; $y < ($src_y); $y++) {
1668 for ($x = 0; $x < ($src_x); $x++) {
1669 $color = imagecolorat($src_img, $x, $y);
1670 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1676 for ($y = 0; $y < ($src_y); $y++) {
1677 for ($x = 0; $x < ($src_x); $x++) {
1678 $color = imagecolorat($src_img, $x, $y);
1679 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1685 for ($y = 0; $y < ($src_y); $y++) {
1686 for ($x = 0; $x < ($src_x); $x++) {
1687 $color = imagecolorat($src_img, $x, $y);
1688 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1702 $img = imagerotate($img, $degrees);
1711 * returns flipped image
1713 * this function will return an either horizontal or
1714 * vertical flipped truecolor image.
1716 private function flipImage($image, $mode)
1718 $w = imagesx($image);
1719 $h = imagesy($image);
1720 $flipped = imagecreatetruecolor($w, $h);
1724 for ($y = 0; $y < $h; $y++) {
1725 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1729 for ($x = 0; $x < $w; $x++) {
1730 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1740 * return all assigned tags for the specified photo
1742 private function get_photo_tags($idx)
1744 $result = $this->db->db_query("
1747 INNER JOIN photo_tags pt
1749 WHERE pt.photo_id='". $idx ."'
1754 while($row = $this->db->db_fetch_object($result))
1755 $tags[$row['id']] = $row['name'];
1759 } // get_photo_tags()
1762 * create on-the-fly images with text within
1764 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1766 if (strlen($color) != 6)
1769 $int = hexdec($color);
1770 $h = imagefontheight($font);
1771 $fw = imagefontwidth($font);
1772 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1773 $lines = count($txt);
1774 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1775 $bg = imagecolorallocate($im, 255, 255, 255);
1776 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1779 foreach ($txt as $text) {
1780 $x = (($w - ($fw * strlen($text))) / 2);
1781 imagestring($im, $font, $x, $y, $text, $color);
1782 $y += ($h + $space);
1785 Header("Content-type: image/png");
1788 } // showTextImage()
1791 * check if all requirements are met
1793 private function checkRequirements()
1795 if(!function_exists("imagecreatefromjpeg")) {
1796 print "PHP GD library extension is missing<br />\n";
1800 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1801 print "PHP SQLite3 library extension is missing<br />\n";
1805 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1806 ini_set('track_errors', 1);
1807 @include_once 'HTML/AJAX/Server.php';
1808 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1809 print "PEAR HTML_AJAX package is missing<br />\n";
1812 @include_once 'Calendar/Calendar.php';
1813 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1814 print "PEAR Calendar package is missing<br />\n";
1817 @include_once 'Console/Getopt.php';
1818 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1819 print "PEAR Console_Getopt package is missing<br />\n";
1822 ini_restore('track_errors');
1829 } // checkRequirements()
1831 private function _debug($text)
1833 if($this->fromcmd) {
1840 * check if specified MIME type is supported
1842 public function checkifImageSupported($mime)
1844 if(in_array($mime, Array("image/jpeg", "image/png", "image/tiff")))
1849 } // checkifImageSupported()
1851 public function _error($text)
1853 switch($this->cfg->logging) {
1856 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1857 print $text ."<br />\n";
1863 error_log($text, 3, $his->cfg->log_file);
1867 $this->runtime_error = true;
1872 * output calendard input fields
1874 private function get_calendar($mode)
1876 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1877 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1878 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1880 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1881 if(!isset($_SESSION[$mode .'_date']))
1882 $output.= " disabled=\"disabled\"";
1884 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1885 if(!isset($_SESSION[$mode .'_date']))
1886 $output.= " disabled=\"disabled\"";
1888 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1889 if(!isset($_SESSION[$mode .'_date']))
1890 $output.= " disabled=\"disabled\"";
1898 * output calendar matrix
1900 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1902 if (!isset($year)) $year = date('Y');
1903 if (!isset($month)) $month = date('m');
1904 if (!isset($day)) $day = date('d');
1909 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1910 require_once CALENDAR_ROOT.'Day.php';
1913 $month = new Calendar_Month_Weekdays($year,$month);
1916 $prevStamp = $month->prevMonth(true);
1917 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1918 $nextStamp = $month->nextMonth(true);
1919 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1921 $selectedDays = array (
1922 new Calendar_Day($year,$month,$day),
1923 new Calendar_Day($year,12,25),
1926 // Build the days in the month
1927 $month->build($selectedDays);
1929 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1930 $this->tmpl->assign('prev_month', $prev);
1931 $this->tmpl->assign('next_month', $next);
1933 while ( $day = $month->fetch() ) {
1935 if(!isset($matrix[$rows]))
1936 $matrix[$rows] = Array();
1940 $dayStamp = $day->thisDay(true);
1941 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1943 // isFirst() to find start of week
1944 if ( $day->isFirst() )
1947 if ( $day->isSelected() ) {
1948 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1949 } else if ( $day->isEmpty() ) {
1950 $string.= "<td> </td>\n";
1952 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1955 // isLast() to find end of week
1956 if ( $day->isLast() )
1957 $string.= "</tr>\n";
1959 $matrix[$rows][$cols] = $string;
1969 $this->tmpl->assign('matrix', $matrix);
1970 $this->tmpl->assign('rows', $rows);
1971 $this->tmpl->show("calendar.tpl");
1973 } // get_calendar_matrix()
1976 * output export page
1978 public function getExport($mode)
1980 $pictures = $this->getPhotoSelection();
1981 $current_tags = $this->getCurrentTags();
1983 foreach($pictures as $picture) {
1985 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1986 if($current_tags != "") {
1987 $orig_url.= "&tags=". $current_tags;
1989 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1990 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1993 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1998 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1999 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2003 // "[%pictureurl% %thumbnailurl%]"
2004 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2007 case 'MoinMoinList':
2008 // " * [%pictureurl% %thumbnailurl%]"
2009 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2020 public function getRSSFeed()
2022 Header("Content-type: text/xml; charset=utf-8");
2023 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2026 xmlns:media="http://search.yahoo.com/mrss/"
2027 xmlns:dc="http://purl.org/dc/elements/1.1/"
2030 <title>phpfspot</title>
2031 <description>phpfspot RSS feed</description>
2032 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2033 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2034 <generator>phpfspot</generator>
2037 $pictures = $this->getPhotoSelection();
2038 $current_tags = $this->getCurrentTags();
2040 foreach($pictures as $picture) {
2042 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2043 if($current_tags != "") {
2044 $orig_url.= "&tags=". $current_tags;
2046 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2047 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2050 $details = $this->get_photo_details($picture);
2052 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2053 $thumb_html = htmlspecialchars("
2054 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2056 ". $details['description']);
2058 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2060 /* get EXIF information if JPEG */
2061 if($details['mime'] == "image/jpeg") {
2062 $meta = $this->get_meta_informations($orig_path);
2065 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2069 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2070 <link><?php print htmlspecialchars($orig_url); ?></link>
2071 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2072 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2074 <?php print $thumb_html; ?>
2076 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2091 * return all selected tags as one string
2093 private function getCurrentTags()
2096 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2097 foreach($_SESSION['selected_tags'] as $tag)
2098 $current_tags.= $tag .",";
2099 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2101 return $current_tags;
2103 } // getCurrentTags()
2106 * return the current photo
2108 public function getCurrentPhoto()
2110 if(isset($_SESSION['current_photo'])) {
2111 print $_SESSION['current_photo'];
2113 } // getCurrentPhoto()
2116 * tells the client browser what to do
2118 * this function is getting called via AJAX by the
2119 * client browsers. it will tell them what they have
2120 * to do next. This is necessary for directly jumping
2121 * into photo index or single photo view when the are
2122 * requested with specific URLs
2124 public function whatToDo()
2126 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2127 return "show_photo";
2129 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2130 return "showpi_tags";
2132 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2136 return "nothing special";
2141 * return the current process-user
2143 private function getuid()
2145 if($uid = posix_getuid()) {
2146 if($user = posix_getpwuid($uid)) {
2147 return $user['name'];
2156 * returns a select-dropdown box to select photo index sort parameters
2158 public function smarty_sort_select_list($params, &$smarty)
2162 foreach($this->sort_orders as $key => $value) {
2163 $output.= "<option value=\"". $key ."\"";
2164 if($key == $_SESSION['sort_order']) {
2165 $output.= " selected=\"selected\"";
2167 $output.= ">". $value ."</option>";
2172 } // smarty_sort_select_list()
2175 * returns the currently selected sort order
2177 private function get_sort_order()
2179 switch($_SESSION['sort_order']) {
2181 return " ORDER BY p.time ASC";
2184 return " ORDER BY p.time DESC";
2187 if($this->dbver < 9) {
2188 return " ORDER BY p.name ASC";
2191 return " ORDER BY basename(p.uri) ASC";
2195 if($this->dbver < 9) {
2196 return " ORDER BY p.name DESC";
2199 return " ORDER BY basename(p.uri) DESC";
2203 return " ORDER BY t.name ASC ,p.time ASC";
2206 return " ORDER BY t.name DESC ,p.time ASC";
2210 } // get_sort_order()
2213 * return the next to be shown slide show image
2215 * this function returns the URL of the next image
2216 * in the slideshow sequence.
2218 public function getNextSlideShowImage()
2220 $all_photos = $this->getPhotoSelection();
2222 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2223 $_SESSION['slideshow_img'] = 0;
2225 $_SESSION['slideshow_img']++;
2227 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2229 } // getNextSlideShowImage()
2232 * return the previous to be shown slide show image
2234 * this function returns the URL of the previous image
2235 * in the slideshow sequence.
2237 public function getPrevSlideShowImage()
2239 $all_photos = $this->getPhotoSelection();
2241 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2242 $_SESSION['slideshow_img'] = 0;
2244 $_SESSION['slideshow_img']--;
2246 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2248 } // getPrevSlideShowImage()
2250 public function resetSlideShow()
2252 if(isset($_SESSION['slideshow_img']))
2253 unset($_SESSION['slideshow_img']);
2255 } // resetSlideShow()
2260 * this function will get all photos from the fspot
2261 * database and randomly return ONE entry
2263 * saddly there is yet no sqlite3 function which returns
2264 * the bulk result in array, so we have to fill up our
2267 public function get_random_photo()
2271 $result = $this->db->db_query("
2276 while($row = $this->db->db_fetch_object($result)) {
2277 array_push($all, $row['id']);
2280 return $all[array_rand($all)];
2282 } // get_random_photo()
2285 * validates provided date
2287 * this function validates if the provided date
2288 * contains a valid date and will return true
2291 public function isValidDate($date_str)
2293 $timestamp = strtotime($date_str);
2295 if(is_numeric($timestamp))
2303 * timestamp to string conversion
2305 private function ts2str($timestamp)
2307 return strftime("%Y-%m-%d", $timestamp);
2310 private function extractTags($tags_str)
2312 $not_validated = split(',', $_GET['tags']);
2313 $validated = array();
2315 foreach($not_validated as $tag) {
2316 if(is_numeric($tag))
2317 array_push($validated, $tag);
2325 * returns the full path to a thumbnail
2327 public function get_thumb_path($width, $photo)
2329 $md5 = $this->getMD5($photo);
2330 $sub_path = substr($md5, 0, 2);
2331 return $this->cfg->thumb_path
2339 } // get_thumb_path()
2342 * returns server's virtual host name
2344 private function get_server_name()
2346 return $_SERVER['SERVER_NAME'];
2347 } // get_server_name()
2350 * returns type of webprotocol which is
2353 private function get_web_protocol()
2355 if(!isset($_SERVER['HTTPS']))
2359 } // get_web_protocol()
2362 * return url to this phpfspot installation
2364 private function get_phpfspot_url()
2366 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2367 } // get_phpfspot_url()
2370 * returns the number of photos which are tagged with $tag_id
2372 public function get_num_photos($tag_id)
2374 if($result = $this->db->db_fetchSingleRow("
2375 SELECT count(*) as number
2378 tag_id LIKE '". $tag_id ."'")) {
2380 return $result['number'];
2386 } // get_num_photos()
2389 * check file exists and is readable
2391 * returns true, if everything is ok, otherwise false
2392 * if $silent is not set, this function will output and
2395 private function check_readable($file, $silent = null)
2397 if(!file_exists($file)) {
2399 print "File \"". $file ."\" does not exist.\n";
2403 if(!is_readable($file)) {
2405 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2411 } // check_readable()
2414 * check if all needed indices are present
2416 * this function checks, if some needed indices are already
2417 * present, or if not, create them on the fly. they are
2418 * necessary to speed up some queries like that one look for
2419 * all tags, when show_tags is specified in the configuration.
2421 private function checkDbIndices()
2423 $result = $this->db->db_exec("
2424 CREATE INDEX IF NOT EXISTS
2431 } // checkDbIndices()
2434 * retrive F-Spot database version
2436 * this function will return the F-Spot database version number
2437 * It is stored within the sqlite3 database in the table meta
2439 public function getFspotDBVersion()
2441 if($result = $this->db->db_fetchSingleRow("
2442 SELECT data as version
2445 name LIKE 'F-Spot Database Version'
2447 return $result['version'];
2451 } // getFspotDBVersion()
2454 * parse the provided URI and will returned the
2457 public function parse_uri($uri, $mode)
2459 if(($components = parse_url($uri)) !== false) {
2463 return basename($components['path']);
2466 return dirname($components['path']);
2469 return $components['path'];
2479 * validate config options
2481 * this function checks if all necessary configuration options are
2482 * specified and set.
2484 private function check_config_options()
2486 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2487 $this->_error("Please set \$page_title in phpfspot_cfg");
2489 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2490 $this->_error("Please set \$base_path in phpfspot_cfg");
2492 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2493 $this->_error("Please set \$web_path in phpfspot_cfg");
2495 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2496 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2498 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2499 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2501 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2502 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2504 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2505 $this->_error("Please set \$db_access in phpfspot_cfg");
2507 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2508 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2510 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2511 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2513 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2514 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2516 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2517 $this->_error("Please set \$photo_width in phpfspot_cfg");
2519 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2520 $this->_error("Please set \$mini_width in phpfspot_cfg");
2522 if(!isset($this->cfg->thumbs_per_page))
2523 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2525 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2526 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2528 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2529 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2531 if(!isset($this->cfg->hide_tags))
2532 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2534 if(!isset($this->cfg->theme_name))
2535 $this->_error("Please set \$theme_name in phpfspot_cfg");
2537 if(!isset($this->cfg->logging))
2538 $this->_error("Please set \$logging in phpfspot_cfg");
2540 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2542 if(!isset($this->cfg->log_file))
2543 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2545 if(!is_writeable($this->cfg->log_file))
2546 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2550 /* check for pending slash on web_path */
2551 if(!preg_match("/\/$/", $this->cfg->web_path))
2552 $this->cfg->web_path.= "/";
2554 return $this->runtime_error;
2556 } // check_config_options()
2559 * cleanup phpfspot own database
2561 * When photos are getting delete from F-Spot, there will remain
2562 * remain some residues in phpfspot own database. This function
2563 * will try to wipe them out.
2565 public function cleanup_phpfspot_db()
2567 $to_delete = Array();
2569 $result = $this->cfg_db->db_query("
2572 ORDER BY img_idx ASC
2575 while($row = $this->cfg_db->db_fetch_object($result)) {
2576 if(!$this->db->db_fetchSingleRow("
2579 WHERE id='". $row['img_idx'] ."'")) {
2581 array_push($to_delete, $row['img_idx'], ',');
2585 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2587 $this->cfg_db->db_exec("
2589 WHERE img_idx IN (". implode($to_delete) .")
2592 } // cleanup_phpfspot_db()
2595 * return first image of the page, the $current photo
2598 * this function is used to find out the first photo of the
2599 * current page, in which the $current photo lies. this is
2600 * used to display the correct photo, when calling showPhotoIndex()
2603 private function getCurrentPage($current, $max)
2605 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2606 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2607 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2613 } // getCurrentPage()