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 /******* Opening F-Spot's sqlite database *********/
75 /* Check if database file is writeable */
76 if(!is_writeable($this->cfg->fspot_db)) {
77 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
81 /* open the database */
82 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
84 /* change sqlite temp directory, if requested */
85 if(isset($this->cfg->sqlite_temp_dir)) {
88 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
92 $this->dbver = $this->getFspotDBVersion();
94 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
95 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
99 if(!is_writeable($this->cfg->thumb_path)) {
100 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
104 /******* Opening phpfspot's sqlite database *********/
106 /* Check if directory where the database file is stored is writeable */
107 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
108 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
112 /* Check if database file is writeable */
113 if(!is_writeable($this->cfg->phpfspot_db)) {
114 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
118 /* open the database */
119 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
121 /* change sqlite temp directory, if requested */
122 if(isset($this->cfg->sqlite_temp_dir)) {
123 $this->cfg_db->db_exec("
125 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
129 /* Check if some tables need to be created */
130 $this->check_config_table();
132 /* include Smarty template engine */
133 if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
136 require $this->cfg->smarty_path .'/libs/Smarty.class.php';
137 /* overload Smarty class if our own template handler */
138 require_once "phpfspot_tmpl.php";
139 $this->tmpl = new PHPFSPOT_TMPL($this);
141 /* check if all necessary indices exist */
142 $this->checkDbIndices();
144 /* if session is not yet started, do it now */
145 if(session_id() == "")
148 if(!isset($_SESSION['tag_condition']))
149 $_SESSION['tag_condition'] = 'or';
151 if(!isset($_SESSION['sort_order']))
152 $_SESSION['sort_order'] = 'date_desc';
154 if(!isset($_SESSION['searchfor_tag']))
155 $_SESSION['searchfor_tag'] = '';
157 // if begin_with is still set but thumbs_per_page is now 0, unset it
158 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
159 unset($_SESSION['begin_with']);
163 public function __destruct()
169 * show - generate html output
171 * this function can be called after the constructor has
172 * prepared everyhing. it will load the index.tpl smarty
173 * template. if necessary it will registere pre-selects
174 * (photo index, photo, tag search, date search) into
177 public function show()
179 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
180 $this->tmpl->assign('page_title', $this->cfg->page_title);
181 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
182 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
184 if(isset($_GET['mode'])) {
186 $_SESSION['start_action'] = $_GET['mode'];
188 switch($_GET['mode']) {
190 if(isset($_GET['tags'])) {
191 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
193 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
194 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
196 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
197 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
201 if(isset($_GET['tags'])) {
202 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
203 $_SESSION['start_action'] = 'showp';
205 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
206 $_SESSION['current_photo'] = $_GET['id'];
207 $_SESSION['start_action'] = 'showp';
209 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
210 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
212 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
213 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
217 $this->tmpl->show("export.tpl");
221 $this->tmpl->show("slideshow.tpl");
225 if(isset($_GET['tags'])) {
226 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
228 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
229 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
231 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
232 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
240 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
241 $this->tmpl->assign('date_search_enabled', true);
243 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
244 $this->tmpl->assign('from_date', $this->get_calendar('from'));
245 $this->tmpl->assign('to_date', $this->get_calendar('to'));
246 $this->tmpl->assign('content_page', 'welcome.tpl');
247 $this->tmpl->show("index.tpl");
252 * get_tags - grab all tags of f-spot's database
254 * this function will get all available tags from
255 * the f-spot database and store them within two
256 * arrays within this class for later usage. in
257 * fact, if the user requests (hide_tags) it will
258 * opt-out some of them.
260 * this function is getting called once by show()
262 private function get_tags()
264 $this->avail_tags = Array();
267 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
270 DISTINCT t1.id as id, t1.name as name
273 INNER JOIN photo_tags
274 pt2 ON pt1.photo_id=pt2.photo_id
280 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
282 t1.sort_priority ASC";
284 $result = $this->db->db_query($query_str);
288 $result = $this->db->db_query("
291 ORDER BY sort_priority ASC
295 while($row = $this->db->db_fetch_object($result)) {
297 $tag_id = $row['id'];
298 $tag_name = $row['name'];
300 /* if the user has specified to ignore this tag in phpfspot's
301 configuration, ignore it here so it does not get added to
304 if(in_array($row['name'], $this->cfg->hide_tags))
307 /* if you include the following if-clause and the user has specified
308 to only show certain tags which are specified in phpfspot's
309 configuration, ignore all others so they will not be added to the
311 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
312 !in_array($row['name'], $this->cfg->show_tags))
316 $this->tags[$tag_id] = $tag_name;
317 $this->avail_tags[$count] = $tag_id;
325 * extract all photo details
327 * retrieve all available details from f-spot's
328 * database and return them as object
330 public function get_photo_details($idx)
332 if($this->dbver < 9) {
334 SELECT p.id, p.name, p.time, p.directory_path, p.description
340 SELECT p.id, p.uri, p.time, p.description
345 /* if show_tags is set, only return details for photos which
346 are specified to be shown
348 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
350 INNER JOIN photo_tags pt
354 WHERE p.id='". $idx ."'
355 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
359 WHERE p.id='". $idx ."'
363 if($result = $this->db->db_query($query_str)) {
365 $row = $this->db->db_fetch_object($result);
367 if($this->dbver < 9) {
368 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
377 } // get_photo_details
380 * returns aligned photo names
382 * this function returns aligned (length) names for
383 * an specific photo. If the length of the name exceeds
384 * $limit the name will be shrinked (...)
386 public function getPhotoName($idx, $limit = 0)
388 if($details = $this->get_photo_details($idx)) {
389 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
390 $name = $this->shrink_text($long_name, $limit);
400 * shrink text according provided limit
402 * If the length of the name exceeds $limit the
403 * text will be shortend and some content in between
404 * will be replaced with "..."
406 private function shrink_text($text, $limit)
408 if($limit != 0 && strlen($text) > $limit) {
409 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
417 * translate f-spoth photo path
419 * as the full-qualified path recorded in the f-spot database
420 * is usally not the same as on the webserver, this function
421 * will replace the path with that one specified in the cfg
423 public function translate_path($path, $width = 0)
425 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
430 * control HTML ouput for a single photo
432 * this function provides all the necessary information
433 * for the single photo template.
435 public function showPhoto($photo)
437 /* get all photos from the current photo selection */
438 $all_photos = $this->getPhotoSelection();
439 $count = count($all_photos);
441 for($i = 0; $i < $count; $i++) {
443 // $get_next will be set, when the photo which has to
444 // be displayed has been found - this means that the
445 // next available is in fact the NEXT image (for the
447 if(isset($get_next)) {
448 $next_img = $all_photos[$i];
452 /* the next photo is our NEXT photo */
453 if($all_photos[$i] == $photo) {
457 $previous_img = $all_photos[$i];
460 if($photo == $all_photos[$i]) {
465 $details = $this->get_photo_details($photo);
472 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
473 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
475 if(!file_exists($orig_path)) {
476 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
480 if(!is_readable($orig_path)) {
481 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
485 /* If the thumbnail doesn't exist yet, try to create it */
486 if(!file_exists($thumb_path)) {
487 $this->gen_thumb($photo, true);
488 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
491 /* get EXIF information if JPEG */
492 if($details['mime'] == "image/jpeg") {
493 $meta = $this->get_meta_informations($orig_path);
496 /* If EXIF data are available, use them */
497 if(isset($meta['ExifImageWidth'])) {
498 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
500 $info = getimagesize($orig_path);
501 $meta_res = $info[0] ."x". $info[1];
504 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
505 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
506 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
508 $extern_link = "index.php?mode=showp&id=". $photo;
509 $current_tags = $this->getCurrentTags();
510 if($current_tags != "") {
511 $extern_link.= "&tags=". $current_tags;
513 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
514 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
517 $this->tmpl->assign('extern_link', $extern_link);
519 if(!file_exists($thumb_path)) {
520 $this->_error("Can't open file ". $thumb_path ."\n");
524 $info = getimagesize($thumb_path);
526 $this->tmpl->assign('description', $details['description']);
527 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
529 $this->tmpl->assign('width', $info[0]);
530 $this->tmpl->assign('height', $info[1]);
531 $this->tmpl->assign('ExifMadeOn', $meta_date);
532 $this->tmpl->assign('ExifMadeWith', $meta_make);
533 $this->tmpl->assign('ExifOrigResolution', $meta_res);
534 $this->tmpl->assign('ExifFileSize', $meta_size);
536 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
537 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
538 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
540 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
541 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
542 $this->tmpl->assign('current_img', $photo);
545 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
546 $this->tmpl->assign('prev_img', $previous_img);
550 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
551 $this->tmpl->assign('next_img', $next_img);
553 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
554 $this->tmpl->assign('photo_number', $i);
555 $this->tmpl->assign('photo_count', count($all_photos));
557 $this->tmpl->show("single_photo.tpl");
562 * all available tags and tag cloud
564 * this function outputs all available tags (time ordered)
565 * and in addition output them as tag cloud (tags which have
566 * many photos will appears more then others)
568 public function getAvailableTags()
570 /* retrive tags from database */
575 $result = $this->db->db_query("
576 SELECT tag_id as id, count(tag_id) as quantity
586 while($row = $this->db->db_fetch_object($result)) {
587 $tags[$row['id']] = $row['quantity'];
590 // change these font sizes if you will
591 $max_size = 125; // max font size in %
592 $min_size = 75; // min font size in %
594 // get the largest and smallest array values
595 $max_qty = max(array_values($tags));
596 $min_qty = min(array_values($tags));
598 // find the range of values
599 $spread = $max_qty - $min_qty;
600 if (0 == $spread) { // we don't want to divide by zero
604 // determine the font-size increment
605 // this is the increase per tag quantity (times used)
606 $step = ($max_size - $min_size)/($spread);
608 // loop through our tag array
609 foreach ($tags as $key => $value) {
611 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
614 // calculate CSS font-size
615 // find the $value in excess of $min_qty
616 // multiply by the font-size increment ($size)
617 // and add the $min_size set above
618 $size = $min_size + (($value - $min_qty) * $step);
619 // uncomment if you want sizes in whole %:
622 if(isset($this->tags[$key])) {
623 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
628 $output = substr($output, 0, strlen($output)-2);
631 } // getAvailableTags()
634 * output all selected tags
636 * this function output all tags which have been selected
637 * by the user. the selected tags are stored in the
638 * session-variable $_SESSION['selected_tags']
640 public function getSelectedTags()
642 /* retrive tags from database */
647 foreach($this->avail_tags as $tag)
649 // return all selected tags
650 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
651 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
656 $output = substr($output, 0, strlen($output)-2);
660 return "no tags selected";
663 } // getSelectedTags()
666 * add tag to users session variable
668 * this function will add the specified to users current
669 * tag selection. if a date search has been made before
670 * it will be now cleared
672 public function addTag($tag)
674 if(!isset($_SESSION['selected_tags']))
675 $_SESSION['selected_tags'] = Array();
677 if(isset($_SESSION['searchfor_tag']))
678 unset($_SESSION['searchfor_tag']);
680 if(!in_array($tag, $_SESSION['selected_tags']))
681 array_push($_SESSION['selected_tags'], $tag);
689 * remove tag to users session variable
691 * this function removes the specified tag from
692 * users current tag selection
694 public function delTag($tag)
696 if(isset($_SESSION['searchfor_tag']))
697 unset($_SESSION['searchfor_tag']);
699 if(isset($_SESSION['selected_tags'])) {
700 $key = array_search($tag, $_SESSION['selected_tags']);
701 unset($_SESSION['selected_tags'][$key]);
702 sort($_SESSION['selected_tags']);
710 * reset tag selection
712 * if there is any tag selection, it will be
715 public function resetTags()
717 if(isset($_SESSION['selected_tags']))
718 unset($_SESSION['selected_tags']);
723 * returns the value for the autocomplet tag-search
725 public function get_xml_tag_list()
727 if(!isset($_GET['search']) || !is_string($_GET['search']))
728 $_GET['search'] = '';
733 /* retrive tags from database */
736 $matched_tags = Array();
738 header("Content-Type: text/xml");
740 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
741 $string.= "<results>\n";
743 foreach($this->avail_tags as $tag)
745 if(!empty($_GET['search']) &&
746 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
747 count($matched_tags) < $length) {
749 $count = $this->get_num_photos($tag);
752 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
755 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
761 /* if we have collected enough items, break out */
762 if(count($matched_tags) >= $length)
766 $string.= "</results>\n";
770 } // get_xml_tag_list()
776 * if a specific photo was requested (external link)
777 * unset the session variable now
779 public function resetPhotoView()
781 if(isset($_SESSION['current_photo']))
782 unset($_SESSION['current_photo']);
784 } // resetPhotoView();
789 * if any tag search has taken place, reset it now
791 public function resetTagSearch()
793 if(isset($_SESSION['searchfor_tag']))
794 unset($_SESSION['searchfor_tag']);
796 } // resetTagSearch()
801 * if any name search has taken place, reset it now
803 public function resetNameSearch()
805 if(isset($_SESSION['searchfor_name']))
806 unset($_SESSION['searchfor_name']);
808 } // resetNameSearch()
813 * if any date search has taken place, reset
816 public function resetDateSearch()
818 if(isset($_SESSION['from_date']))
819 unset($_SESSION['from_date']);
820 if(isset($_SESSION['to_date']))
821 unset($_SESSION['to_date']);
823 } // resetDateSearch();
826 * return all photo according selection
828 * this function returns all photos based on
829 * the tag-selection, tag- or date-search.
830 * the tag-search also has to take care of AND
831 * and OR conjunctions
833 public function getPhotoSelection()
835 $matched_photos = Array();
836 $additional_where_cond = "";
838 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
839 $from_date = $_SESSION['from_date'];
840 $to_date = $_SESSION['to_date'];
841 $additional_where_cond.= "
842 p.time>='". $from_date ."'
844 p.time<='". $to_date ."'
848 if(isset($_SESSION['searchfor_name'])) {
849 if($this->dbver < 9) {
850 $additional_where_cond.= "
852 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
854 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
859 $additional_where_cond.= "
861 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
863 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
869 if(isset($_SESSION['sort_order'])) {
870 $order_str = $this->get_sort_order();
873 /* return a search result */
874 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
876 SELECT DISTINCT pt1.photo_id
878 INNER JOIN photo_tags pt2
879 ON pt1.photo_id=pt2.photo_id
886 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
888 if(isset($additional_where_cond) && !empty($additional_where_cond))
889 $query_str.= "AND ". $additional_where_cond ." ";
891 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
892 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
895 if(isset($order_str))
896 $query_str.= $order_str;
898 $result = $this->db->db_query($query_str);
899 while($row = $this->db->db_fetch_object($result)) {
900 array_push($matched_photos, $row['photo_id']);
902 return $matched_photos;
905 /* return according the selected tags */
906 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
908 foreach($_SESSION['selected_tags'] as $tag)
909 $selected.= $tag .",";
910 $selected = substr($selected, 0, strlen($selected)-1);
912 /* photo has to match at least on of the selected tags */
913 if($_SESSION['tag_condition'] == 'or') {
915 SELECT DISTINCT pt1.photo_id
917 INNER JOIN photo_tags pt2
918 ON pt1.photo_id=pt2.photo_id
923 WHERE pt1.tag_id IN (". $selected .")
925 if(isset($additional_where_cond) && !empty($additional_where_cond))
926 $query_str.= "AND ". $additional_where_cond ." ";
928 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
929 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
932 if(isset($order_str))
933 $query_str.= $order_str;
935 /* photo has to match all selected tags */
936 elseif($_SESSION['tag_condition'] == 'and') {
938 if(count($_SESSION['selected_tags']) >= 32) {
939 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
940 print "evaluate your tag selection. Please remove some tags from your selection.\n";
944 /* Join together a table looking like
946 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
948 so the query can quickly return all images matching the
949 selected tags in an AND condition
954 SELECT DISTINCT pt1.photo_id
958 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
965 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
967 INNER JOIN photo_tags pt". ($i+2) ."
968 ON pt1.photo_id=pt". ($i+2) .".photo_id
975 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
976 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
978 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
981 if(isset($additional_where_cond) && !empty($additional_where_cond))
982 $query_str.= "AND ". $additional_where_cond;
984 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
985 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
988 if(isset($order_str))
989 $query_str.= $order_str;
993 $result = $this->db->db_query($query_str);
994 while($row = $this->db->db_fetch_object($result)) {
995 array_push($matched_photos, $row['photo_id']);
997 return $matched_photos;
1000 /* return all available photos */
1002 SELECT DISTINCT p.id
1004 LEFT JOIN photo_tags pt
1010 if(isset($additional_where_cond) && !empty($additional_where_cond))
1011 $query_str.= "WHERE ". $additional_where_cond ." ";
1013 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1014 if(isset($additional_where_cond) && !empty($additional_where_cond))
1015 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1017 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1020 if(isset($order_str))
1021 $query_str.= $order_str;
1023 $result = $this->db->db_query($query_str);
1024 while($row = $this->db->db_fetch_object($result)) {
1025 array_push($matched_photos, $row['id']);
1027 return $matched_photos;
1029 } // getPhotoSelection()
1032 * control HTML ouput for photo index
1034 * this function provides all the necessary information
1035 * for the photo index template.
1037 public function showPhotoIndex()
1039 $photos = $this->getPhotoSelection();
1041 $count = count($photos);
1043 /* if all thumbnails should be shown on one page */
1044 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1048 /* thumbnails should be splitted up in several pages */
1049 elseif($this->cfg->thumbs_per_page > 0) {
1051 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1055 $begin_with = $_SESSION['begin_with'];
1058 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1062 $images[$thumbs] = Array();
1063 $img_height[$thumbs] = Array();
1064 $img_width[$thumbs] = Array();
1065 $img_id[$thumbs] = Array();
1066 $img_name[$thumbs] = Array();
1067 $img_fullname[$thumbs] = Array();
1068 $img_title = Array();
1070 for($i = $begin_with; $i < $end_with; $i++) {
1072 if(isset($photos[$i])) {
1074 $images[$thumbs] = $photos[$i];
1075 $img_id[$thumbs] = $i;
1076 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1077 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1078 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1080 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1082 if(file_exists($thumb_path)) {
1083 $info = getimagesize($thumb_path);
1084 $img_width[$thumbs] = $info[0];
1085 $img_height[$thumbs] = $info[1];
1091 // +1 for for smarty's selection iteration
1094 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1095 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1097 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1098 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1099 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1102 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1103 $this->tmpl->assign('tag_result', 1);
1106 /* do we have to display the page selector ? */
1107 if($this->cfg->thumbs_per_page != 0) {
1111 /* calculate the page switchers */
1112 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1113 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1115 if($begin_with != 0)
1116 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1117 if($end_with < $count)
1118 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1120 $photo_per_page = $this->cfg->thumbs_per_page;
1121 $last_page = ceil($count / $photo_per_page);
1123 /* get the current selected page */
1124 if($begin_with == 0) {
1128 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1135 for($i = 1; $i <= $last_page; $i++) {
1137 if($current_page == $i)
1138 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1139 elseif($current_page-1 == $i || $current_page+1 == $i)
1140 $style = "style=\"font-size: 105%;\"";
1141 elseif(($current_page-5 >= $i) && ($i != 1) ||
1142 ($current_page+5 <= $i) && ($i != $last_page))
1143 $style = "style=\"font-size: 75%;\"";
1147 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1150 $select.= ">". $i ."</a> ";
1152 // until 9 pages we show the selector from 1-9
1153 if($last_page <= 9) {
1154 $page_select.= $select;
1157 if($i == 1 /* first page */ ||
1158 $i == $last_page /* last page */ ||
1159 $i == $current_page /* current page */ ||
1160 $i == ceil($last_page * 0.25) /* first quater */ ||
1161 $i == ceil($last_page * 0.5) /* half */ ||
1162 $i == ceil($last_page * 0.75) /* third quater */ ||
1163 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1164 (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 */ ||
1165 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1166 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1168 $page_select.= $select;
1176 $page_select.= "......... ";
1181 /* only show the page selector if we have more then one page */
1183 $this->tmpl->assign('page_selector', $page_select);
1187 $current_tags = $this->getCurrentTags();
1188 $extern_link = "index.php?mode=showpi";
1189 $rss_link = "index.php?mode=rss";
1190 if($current_tags != "") {
1191 $extern_link.= "&tags=". $current_tags;
1192 $rss_link.= "&tags=". $current_tags;
1194 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1195 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1196 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1199 $export_link = "index.php?mode=export";
1200 $slideshow_link = "index.php?mode=slideshow";
1202 $this->tmpl->assign('extern_link', $extern_link);
1203 $this->tmpl->assign('slideshow_link', $slideshow_link);
1204 $this->tmpl->assign('export_link', $export_link);
1205 $this->tmpl->assign('rss_link', $rss_link);
1206 $this->tmpl->assign('count', $count);
1207 $this->tmpl->assign('width', $this->cfg->thumb_width);
1208 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1209 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1210 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1211 $this->tmpl->assign('images', $images);
1212 $this->tmpl->assign('img_width', $img_width);
1213 $this->tmpl->assign('img_height', $img_height);
1214 $this->tmpl->assign('img_id', $img_id);
1215 $this->tmpl->assign('img_name', $img_name);
1216 $this->tmpl->assign('img_fullname', $img_fullname);
1217 $this->tmpl->assign('img_title', $img_title);
1218 $this->tmpl->assign('thumbs', $thumbs);
1220 $this->tmpl->show("photo_index.tpl");
1222 /* if we are returning to photo index from an photo-view,
1223 scroll the window to the last shown photo-thumbnail.
1224 after this, unset the last_photo session variable.
1226 if(isset($_SESSION['last_photo'])) {
1227 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1228 unset($_SESSION['last_photo']);
1231 } // showPhotoIndex()
1234 * show credit template
1236 public function showCredits()
1238 $this->tmpl->assign('version', $this->cfg->version);
1239 $this->tmpl->assign('product', $this->cfg->product);
1240 $this->tmpl->assign('db_version', $this->dbver);
1241 $this->tmpl->show("credits.tpl");
1246 * create_thumbnails for the requested width
1248 * this function creates image thumbnails of $orig_image
1249 * stored as $thumb_image. It will check if the image is
1250 * in a supported format, if necessary rotate the image
1251 * (based on EXIF orientation meta headers) and re-sizing.
1253 public function create_thumbnail($orig_image, $thumb_image, $width)
1255 if(!file_exists($orig_image)) {
1259 $details = getimagesize($orig_image);
1261 /* check if original photo is a support image type */
1262 if(!$this->checkifImageSupported($details['mime']))
1265 switch($details['mime']) {
1269 $meta = $this->get_meta_informations($orig_image);
1275 switch($meta['Orientation']) {
1276 case 1: /* top, left */
1277 /* nothing to do */ break;
1278 case 2: /* top, right */
1279 $rotate = 0; $flip_hori = true; break;
1280 case 3: /* bottom, left */
1281 $rotate = 180; break;
1282 case 4: /* bottom, right */
1283 $flip_vert = true; break;
1284 case 5: /* left side, top */
1285 $rotate = 90; $flip_vert = true; break;
1286 case 6: /* right side, top */
1287 $rotate = 90; break;
1288 case 7: /* left side, bottom */
1289 $rotate = 270; $flip_vert = true; break;
1290 case 8: /* right side, bottom */
1291 $rotate = 270; break;
1294 $src_img = @imagecreatefromjpeg($orig_image);
1299 $src_img = @imagecreatefrompng($orig_image);
1305 print "Can't load image from ". $orig_image ."\n";
1309 /* grabs the height and width */
1310 $cur_width = imagesx($src_img);
1311 $cur_height = imagesy($src_img);
1313 // If requested width is more then the actual image width,
1314 // do not generate a thumbnail, instead safe the original
1315 // as thumbnail but with lower quality. But if the image
1316 // is to heigh too, then we still have to resize it.
1317 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1318 $result = imagejpeg($src_img, $thumb_image, 75);
1319 imagedestroy($src_img);
1323 // If the image will be rotate because EXIF orientation said so
1324 // 'virtually rotate' the image for further calculations
1325 if($rotate == 90 || $rotate == 270) {
1327 $cur_width = $cur_height;
1331 /* calculates aspect ratio */
1332 $aspect_ratio = $cur_height / $cur_width;
1335 if($aspect_ratio < 1) {
1337 $new_h = abs($new_w * $aspect_ratio);
1339 /* 'virtually' rotate the image and calculate it's ratio */
1340 $tmp_w = $cur_height;
1341 $tmp_h = $cur_width;
1342 /* now get the ratio from the 'rotated' image */
1343 $tmp_ratio = $tmp_h/$tmp_w;
1344 /* now calculate the new dimensions */
1346 $tmp_h = abs($tmp_w * $tmp_ratio);
1348 // now that we know, how high they photo should be, if it
1349 // gets rotated, use this high to scale the image
1351 $new_w = abs($new_h / $aspect_ratio);
1353 // If the image will be rotate because EXIF orientation said so
1354 // now 'virtually rotate' back the image for the image manipulation
1355 if($rotate == 90 || $rotate == 270) {
1362 /* creates new image of that size */
1363 $dst_img = imagecreatetruecolor($new_w, $new_h);
1365 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1367 /* copies resized portion of original image into new image */
1368 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1370 /* needs the image to be flipped horizontal? */
1372 $this->_debug("(FLIP)");
1373 $dst_img = $this->flipImage($dst_img, 'hori');
1375 /* needs the image to be flipped vertical? */
1377 $this->_debug("(FLIP)");
1378 $dst_img = $this->flipImage($dst_img, 'vert');
1382 $this->_debug("(ROTATE)");
1383 $dst_img = $this->rotateImage($dst_img, $rotate);
1386 /* write down new generated file */
1387 $result = imagejpeg($dst_img, $thumb_image, 75);
1389 /* free your mind */
1390 imagedestroy($dst_img);
1391 imagedestroy($src_img);
1393 if($result === false) {
1394 print "Can't write thumbnail ". $thumb_image ."\n";
1400 } // create_thumbnail()
1403 * return all exif meta data from the file
1405 public function get_meta_informations($file)
1407 return exif_read_data($file);
1409 } // get_meta_informations()
1412 * create phpfspot own sqlite database
1414 * this function creates phpfspots own sqlite database
1415 * if it does not exist yet. this own is used to store
1416 * some necessary informations (md5 sum's, ...).
1418 public function check_config_table()
1420 // if the config table doesn't exist yet, create it
1421 if(!$this->cfg_db->db_check_table_exists("images")) {
1422 $this->cfg_db->db_exec("
1423 CREATE TABLE images (
1424 img_idx int primary key,
1430 } // check_config_table
1433 * Generates a thumbnail from photo idx
1435 * This function will generate JPEG thumbnails from provided F-Spot photo
1438 * 1. Check if all thumbnail generations (width) are already in place and
1440 * 2. Check if the md5sum of the original file has changed
1441 * 3. Generate the thumbnails if needed
1443 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1447 $resolutions = Array(
1448 $this->cfg->thumb_width,
1449 $this->cfg->photo_width,
1450 $this->cfg->mini_width,
1453 /* get details from F-Spot's database */
1454 $details = $this->get_photo_details($idx);
1456 /* calculate file MD5 sum */
1457 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1459 if(!file_exists($full_path)) {
1460 $this->_error("File ". $full_path ." does not exist\n");
1464 if(!is_readable($full_path)) {
1465 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1469 $file_md5 = md5_file($full_path);
1471 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1475 foreach($resolutions as $resolution) {
1477 $generate_it = false;
1479 $thumb_sub_path = substr($file_md5, 0, 2);
1480 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1482 /* if thumbnail-subdirectory does not exist yet, create it */
1483 if(!file_exists(dirname($thumb_path))) {
1484 mkdir(dirname($thumb_path), 0755);
1487 /* if the thumbnail file doesn't exist, create it */
1488 if(!file_exists($thumb_path)) {
1489 $generate_it = true;
1491 /* if the file hasn't changed there is no need to regen the thumb */
1492 elseif($file_md5 != $this->getMD5($idx) || $force) {
1493 $generate_it = true;
1496 if($generate_it || $overwrite) {
1498 $this->_debug(" ". $resolution ."px");
1499 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1507 $this->_debug(" already exist");
1510 /* set the new/changed MD5 sum for the current photo */
1512 $this->setMD5($idx, $file_md5);
1515 $this->_debug("\n");
1520 * returns stored md5 sum for a specific photo
1522 * this function queries the phpfspot database for a
1523 * stored MD5 checksum of the specified photo
1525 public function getMD5($idx)
1527 $result = $this->cfg_db->db_query("
1530 WHERE img_idx='". $idx ."'
1536 $img = $this->cfg_db->db_fetch_object($result);
1537 return $img['img_md5'];
1542 * set MD5 sum for the specific photo
1544 private function setMD5($idx, $md5)
1546 $result = $this->cfg_db->db_exec("
1547 REPLACE INTO images (img_idx, img_md5)
1548 VALUES ('". $idx ."', '". $md5 ."')
1554 * store current tag condition
1556 * this function stores the current tag condition
1557 * (AND or OR) in the users session variables
1559 public function setTagCondition($mode)
1561 $_SESSION['tag_condition'] = $mode;
1565 } // setTagCondition()
1568 * invoke tag & date search
1570 * this function will return all matching tags and store
1571 * them in the session variable selected_tags. furthermore
1572 * it also handles the date search.
1573 * getPhotoSelection() will then only return the matching
1576 public function startSearch()
1578 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1579 $from = $_POST['from'];
1581 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1585 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1586 $searchfor_tag = $_POST['for_tag'];
1587 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1590 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1591 $searchfor_name = $_POST['for_name'];
1592 $_SESSION['searchfor_name'] = $_POST['for_name'];
1597 if(isset($from) && !empty($from))
1598 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1600 unset($_SESSION['from_date']);
1602 if(isset($to) && !empty($to))
1603 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1605 unset($_SESSION['to_date']);
1607 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1608 /* new search, reset the current selected tags */
1609 $_SESSION['selected_tags'] = Array();
1610 foreach($this->avail_tags as $tag) {
1611 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1612 array_push($_SESSION['selected_tags'], $tag);
1621 * updates sort order in session variable
1623 * this function is invoked by RPC and will sort the requested
1624 * sort order in the session variable.
1626 public function updateSortOrder($order)
1628 if(isset($this->sort_orders[$order])) {
1629 $_SESSION['sort_order'] = $order;
1633 return "unkown error";
1635 } // updateSortOrder()
1640 * this function rotates the image according the
1643 private function rotateImage($img, $degrees)
1645 if(function_exists("imagerotate")) {
1646 $img = imagerotate($img, $degrees, 0);
1648 function imagerotate($src_img, $angle)
1650 $src_x = imagesx($src_img);
1651 $src_y = imagesy($src_img);
1652 if ($angle == 180) {
1656 elseif ($src_x <= $src_y) {
1660 elseif ($src_x >= $src_y) {
1665 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1666 imagealphablending($rotate, false);
1671 for ($y = 0; $y < ($src_y); $y++) {
1672 for ($x = 0; $x < ($src_x); $x++) {
1673 $color = imagecolorat($src_img, $x, $y);
1674 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1680 for ($y = 0; $y < ($src_y); $y++) {
1681 for ($x = 0; $x < ($src_x); $x++) {
1682 $color = imagecolorat($src_img, $x, $y);
1683 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1689 for ($y = 0; $y < ($src_y); $y++) {
1690 for ($x = 0; $x < ($src_x); $x++) {
1691 $color = imagecolorat($src_img, $x, $y);
1692 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1706 $img = imagerotate($img, $degrees);
1715 * returns flipped image
1717 * this function will return an either horizontal or
1718 * vertical flipped truecolor image.
1720 private function flipImage($image, $mode)
1722 $w = imagesx($image);
1723 $h = imagesy($image);
1724 $flipped = imagecreatetruecolor($w, $h);
1728 for ($y = 0; $y < $h; $y++) {
1729 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1733 for ($x = 0; $x < $w; $x++) {
1734 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1744 * return all assigned tags for the specified photo
1746 private function get_photo_tags($idx)
1748 $result = $this->db->db_query("
1751 INNER JOIN photo_tags pt
1753 WHERE pt.photo_id='". $idx ."'
1758 while($row = $this->db->db_fetch_object($result))
1759 $tags[$row['id']] = $row['name'];
1763 } // get_photo_tags()
1766 * create on-the-fly images with text within
1768 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1770 if (strlen($color) != 6)
1773 $int = hexdec($color);
1774 $h = imagefontheight($font);
1775 $fw = imagefontwidth($font);
1776 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1777 $lines = count($txt);
1778 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1779 $bg = imagecolorallocate($im, 255, 255, 255);
1780 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1783 foreach ($txt as $text) {
1784 $x = (($w - ($fw * strlen($text))) / 2);
1785 imagestring($im, $font, $x, $y, $text, $color);
1786 $y += ($h + $space);
1789 Header("Content-type: image/png");
1792 } // showTextImage()
1795 * check if all requirements are met
1797 private function checkRequirements()
1799 if(!function_exists("imagecreatefromjpeg")) {
1800 print "PHP GD library extension is missing<br />\n";
1804 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1805 print "PHP SQLite3 library extension is missing<br />\n";
1809 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1810 ini_set('track_errors', 1);
1811 @include_once 'HTML/AJAX/Server.php';
1812 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1813 print "PEAR HTML_AJAX package is missing<br />\n";
1816 @include_once 'Calendar/Calendar.php';
1817 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1818 print "PEAR Calendar package is missing<br />\n";
1821 @include_once 'Console/Getopt.php';
1822 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1823 print "PEAR Console_Getopt package is missing<br />\n";
1826 ini_restore('track_errors');
1833 } // checkRequirements()
1835 private function _debug($text)
1837 if($this->fromcmd) {
1844 * check if specified MIME type is supported
1846 public function checkifImageSupported($mime)
1848 if(in_array($mime, Array("image/jpeg", "image/png")))
1853 } // checkifImageSupported()
1855 public function _error($text)
1857 switch($this->cfg->logging) {
1860 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1861 print $text ."<br />\n";
1867 error_log($text, 3, $his->cfg->log_file);
1871 $this->runtime_error = true;
1876 * output calendard input fields
1878 private function get_calendar($mode)
1880 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1881 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1882 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1884 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1885 if(!isset($_SESSION[$mode .'_date']))
1886 $output.= " disabled=\"disabled\"";
1888 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1889 if(!isset($_SESSION[$mode .'_date']))
1890 $output.= " disabled=\"disabled\"";
1892 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1893 if(!isset($_SESSION[$mode .'_date']))
1894 $output.= " disabled=\"disabled\"";
1902 * output calendar matrix
1904 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1906 if (!isset($year)) $year = date('Y');
1907 if (!isset($month)) $month = date('m');
1908 if (!isset($day)) $day = date('d');
1913 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1914 require_once CALENDAR_ROOT.'Day.php';
1917 $month = new Calendar_Month_Weekdays($year,$month);
1920 $prevStamp = $month->prevMonth(true);
1921 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1922 $nextStamp = $month->nextMonth(true);
1923 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1925 $selectedDays = array (
1926 new Calendar_Day($year,$month,$day),
1927 new Calendar_Day($year,12,25),
1930 // Build the days in the month
1931 $month->build($selectedDays);
1933 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1934 $this->tmpl->assign('prev_month', $prev);
1935 $this->tmpl->assign('next_month', $next);
1937 while ( $day = $month->fetch() ) {
1939 if(!isset($matrix[$rows]))
1940 $matrix[$rows] = Array();
1944 $dayStamp = $day->thisDay(true);
1945 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1947 // isFirst() to find start of week
1948 if ( $day->isFirst() )
1951 if ( $day->isSelected() ) {
1952 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1953 } else if ( $day->isEmpty() ) {
1954 $string.= "<td> </td>\n";
1956 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1959 // isLast() to find end of week
1960 if ( $day->isLast() )
1961 $string.= "</tr>\n";
1963 $matrix[$rows][$cols] = $string;
1973 $this->tmpl->assign('matrix', $matrix);
1974 $this->tmpl->assign('rows', $rows);
1975 $this->tmpl->show("calendar.tpl");
1977 } // get_calendar_matrix()
1980 * output export page
1982 public function getExport($mode)
1984 $pictures = $this->getPhotoSelection();
1985 $current_tags = $this->getCurrentTags();
1987 foreach($pictures as $picture) {
1989 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1990 if($current_tags != "") {
1991 $orig_url.= "&tags=". $current_tags;
1993 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1994 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1997 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2002 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2003 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2007 // "[%pictureurl% %thumbnailurl%]"
2008 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2011 case 'MoinMoinList':
2012 // " * [%pictureurl% %thumbnailurl%]"
2013 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2024 public function getRSSFeed()
2026 Header("Content-type: text/xml; charset=utf-8");
2027 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2030 xmlns:media="http://search.yahoo.com/mrss/"
2031 xmlns:dc="http://purl.org/dc/elements/1.1/"
2034 <title>phpfspot</title>
2035 <description>phpfspot RSS feed</description>
2036 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2037 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2038 <generator>phpfspot</generator>
2041 $pictures = $this->getPhotoSelection();
2042 $current_tags = $this->getCurrentTags();
2044 foreach($pictures as $picture) {
2046 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2047 if($current_tags != "") {
2048 $orig_url.= "&tags=". $current_tags;
2050 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2051 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2054 $details = $this->get_photo_details($picture);
2056 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2057 $thumb_html = htmlspecialchars("
2058 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2060 ". $details['description']);
2062 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2064 /* get EXIF information if JPEG */
2065 if($details['mime'] == "image/jpeg") {
2066 $meta = $this->get_meta_informations($orig_path);
2069 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2073 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2074 <link><?php print htmlspecialchars($orig_url); ?></link>
2075 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2076 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2078 <?php print $thumb_html; ?>
2080 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2095 * return all selected tags as one string
2097 private function getCurrentTags()
2100 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2101 foreach($_SESSION['selected_tags'] as $tag)
2102 $current_tags.= $tag .",";
2103 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2105 return $current_tags;
2107 } // getCurrentTags()
2110 * return the current photo
2112 public function getCurrentPhoto()
2114 if(isset($_SESSION['current_photo'])) {
2115 print $_SESSION['current_photo'];
2117 } // getCurrentPhoto()
2120 * tells the client browser what to do
2122 * this function is getting called via AJAX by the
2123 * client browsers. it will tell them what they have
2124 * to do next. This is necessary for directly jumping
2125 * into photo index or single photo view when the are
2126 * requested with specific URLs
2128 public function whatToDo()
2130 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2131 return "show_photo";
2133 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2134 return "showpi_tags";
2136 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2140 return "nothing special";
2145 * return the current process-user
2147 private function getuid()
2149 if($uid = posix_getuid()) {
2150 if($user = posix_getpwuid($uid)) {
2151 return $user['name'];
2160 * returns a select-dropdown box to select photo index sort parameters
2162 public function smarty_sort_select_list($params, &$smarty)
2166 foreach($this->sort_orders as $key => $value) {
2167 $output.= "<option value=\"". $key ."\"";
2168 if($key == $_SESSION['sort_order']) {
2169 $output.= " selected=\"selected\"";
2171 $output.= ">". $value ."</option>";
2176 } // smarty_sort_select_list()
2179 * returns the currently selected sort order
2181 private function get_sort_order()
2183 switch($_SESSION['sort_order']) {
2185 return " ORDER BY p.time ASC";
2188 return " ORDER BY p.time DESC";
2191 if($this->dbver < 9) {
2192 return " ORDER BY p.name ASC";
2195 return " ORDER BY basename(p.uri) ASC";
2199 if($this->dbver < 9) {
2200 return " ORDER BY p.name DESC";
2203 return " ORDER BY basename(p.uri) DESC";
2207 return " ORDER BY t.name ASC ,p.time ASC";
2210 return " ORDER BY t.name DESC ,p.time ASC";
2214 } // get_sort_order()
2217 * return the next to be shown slide show image
2219 * this function returns the URL of the next image
2220 * in the slideshow sequence.
2222 public function getNextSlideShowImage()
2224 $all_photos = $this->getPhotoSelection();
2226 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2227 $_SESSION['slideshow_img'] = 0;
2229 $_SESSION['slideshow_img']++;
2231 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2233 } // getNextSlideShowImage()
2236 * return the previous to be shown slide show image
2238 * this function returns the URL of the previous image
2239 * in the slideshow sequence.
2241 public function getPrevSlideShowImage()
2243 $all_photos = $this->getPhotoSelection();
2245 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2246 $_SESSION['slideshow_img'] = 0;
2248 $_SESSION['slideshow_img']--;
2250 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2252 } // getPrevSlideShowImage()
2254 public function resetSlideShow()
2256 if(isset($_SESSION['slideshow_img']))
2257 unset($_SESSION['slideshow_img']);
2259 } // resetSlideShow()
2264 * this function will get all photos from the fspot
2265 * database and randomly return ONE entry
2267 * saddly there is yet no sqlite3 function which returns
2268 * the bulk result in array, so we have to fill up our
2271 public function get_random_photo()
2275 $result = $this->db->db_query("
2280 while($row = $this->db->db_fetch_object($result)) {
2281 array_push($all, $row['id']);
2284 return $all[array_rand($all)];
2286 } // get_random_photo()
2289 * validates provided date
2291 * this function validates if the provided date
2292 * contains a valid date and will return true
2295 public function isValidDate($date_str)
2297 $timestamp = strtotime($date_str);
2299 if(is_numeric($timestamp))
2307 * timestamp to string conversion
2309 private function ts2str($timestamp)
2311 return strftime("%Y-%m-%d", $timestamp);
2314 private function extractTags($tags_str)
2316 $not_validated = split(',', $_GET['tags']);
2317 $validated = array();
2319 foreach($not_validated as $tag) {
2320 if(is_numeric($tag))
2321 array_push($validated, $tag);
2329 * returns the full path to a thumbnail
2331 public function get_thumb_path($width, $photo)
2333 $md5 = $this->getMD5($photo);
2334 $sub_path = substr($md5, 0, 2);
2335 return $this->cfg->thumb_path
2343 } // get_thumb_path()
2346 * returns server's virtual host name
2348 private function get_server_name()
2350 return $_SERVER['SERVER_NAME'];
2351 } // get_server_name()
2354 * returns type of webprotocol which is
2357 private function get_web_protocol()
2359 if(!isset($_SERVER['HTTPS']))
2363 } // get_web_protocol()
2366 * return url to this phpfspot installation
2368 private function get_phpfspot_url()
2370 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2371 } // get_phpfspot_url()
2374 * returns the number of photos which are tagged with $tag_id
2376 public function get_num_photos($tag_id)
2378 if($result = $this->db->db_fetchSingleRow("
2379 SELECT count(*) as number
2382 tag_id LIKE '". $tag_id ."'")) {
2384 return $result['number'];
2390 } // get_num_photos()
2393 * check file exists and is readable
2395 * returns true, if everything is ok, otherwise false
2396 * if $silent is not set, this function will output and
2399 private function check_readable($file, $silent = null)
2401 if(!file_exists($file)) {
2403 print "File \"". $file ."\" does not exist.\n";
2407 if(!is_readable($file)) {
2409 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2415 } // check_readable()
2418 * check if all needed indices are present
2420 * this function checks, if some needed indices are already
2421 * present, or if not, create them on the fly. they are
2422 * necessary to speed up some queries like that one look for
2423 * all tags, when show_tags is specified in the configuration.
2425 private function checkDbIndices()
2427 $result = $this->db->db_exec("
2428 CREATE INDEX IF NOT EXISTS
2435 } // checkDbIndices()
2438 * retrive F-Spot database version
2440 * this function will return the F-Spot database version number
2441 * It is stored within the sqlite3 database in the table meta
2443 public function getFspotDBVersion()
2445 if($result = $this->db->db_fetchSingleRow("
2446 SELECT data as version
2449 name LIKE 'F-Spot Database Version'
2451 return $result['version'];
2455 } // getFspotDBVersion()
2458 * parse the provided URI and will returned the
2461 public function parse_uri($uri, $mode)
2463 if(($components = parse_url($uri)) !== false) {
2467 return basename($components['path']);
2470 return dirname($components['path']);
2473 return $components['path'];
2483 * validate config options
2485 * this function checks if all necessary configuration options are
2486 * specified and set.
2488 private function check_config_options()
2490 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2491 $this->_error("Please set \$page_title in phpfspot_cfg");
2493 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2494 $this->_error("Please set \$base_path in phpfspot_cfg");
2496 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2497 $this->_error("Please set \$web_path in phpfspot_cfg");
2499 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2500 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2502 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2503 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2505 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2506 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2508 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2509 $this->_error("Please set \$db_access in phpfspot_cfg");
2511 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2512 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2514 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2515 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2517 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2518 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2520 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2521 $this->_error("Please set \$photo_width in phpfspot_cfg");
2523 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2524 $this->_error("Please set \$mini_width in phpfspot_cfg");
2526 if(!isset($this->cfg->thumbs_per_page))
2527 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2529 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2530 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2532 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2533 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2535 if(!isset($this->cfg->hide_tags))
2536 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2538 if(!isset($this->cfg->theme_name))
2539 $this->_error("Please set \$theme_name in phpfspot_cfg");
2541 if(!isset($this->cfg->logging))
2542 $this->_error("Please set \$logging in phpfspot_cfg");
2544 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2546 if(!isset($this->cfg->log_file))
2547 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2549 if(!is_writeable($this->cfg->log_file))
2550 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2554 /* check for pending slash on web_path */
2555 if(!preg_match("/\/$/", $this->cfg->web_path))
2556 $this->cfg->web_path.= "/";
2558 return $this->runtime_error;
2560 } // check_config_options()
2563 * cleanup phpfspot own database
2565 * When photos are getting delete from F-Spot, there will remain
2566 * remain some residues in phpfspot own database. This function
2567 * will try to wipe them out.
2569 public function cleanup_phpfspot_db()
2571 $to_delete = Array();
2573 $result = $this->cfg_db->db_query("
2576 ORDER BY img_idx ASC
2579 while($row = $this->cfg_db->db_fetch_object($result)) {
2580 if(!$this->db->db_fetchSingleRow("
2583 WHERE id='". $row['img_idx'] ."'")) {
2585 array_push($to_delete, $row['img_idx'], ',');
2589 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2591 $this->cfg_db->db_exec("
2593 WHERE img_idx IN (". implode($to_delete) .")
2596 } // cleanup_phpfspot_db()
2599 * return first image of the page, the $current photo
2602 * this function is used to find out the first photo of the
2603 * current page, in which the $current photo lies. this is
2604 * used to display the correct photo, when calling showPhotoIndex()
2607 private function getCurrentPage($current, $max)
2609 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2610 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2611 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2617 } // getCurrentPage()