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->check_requirements()) {
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 /* overload Smarty class with our own template handler */
133 require_once "phpfspot_tmpl.php";
134 $this->tmpl = new PHPFSPOT_TMPL($this);
136 /* check if all necessary indices exist */
137 $this->checkDbIndices();
139 /* if session is not yet started, do it now */
140 if(session_id() == "")
143 if(!isset($_SESSION['tag_condition']))
144 $_SESSION['tag_condition'] = 'or';
146 if(!isset($_SESSION['sort_order']))
147 $_SESSION['sort_order'] = 'date_desc';
149 if(!isset($_SESSION['searchfor_tag']))
150 $_SESSION['searchfor_tag'] = '';
152 // if begin_with is still set but thumbs_per_page is now 0, unset it
153 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
154 unset($_SESSION['begin_with']);
158 public function __destruct()
164 * show - generate html output
166 * this function can be called after the constructor has
167 * prepared everyhing. it will load the index.tpl smarty
168 * template. if necessary it will registere pre-selects
169 * (photo index, photo, tag search, date search) into
172 public function show()
174 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
175 $this->tmpl->assign('page_title', $this->cfg->page_title);
176 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
177 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
179 if(isset($_GET['mode'])) {
181 $_SESSION['start_action'] = $_GET['mode'];
183 switch($_GET['mode']) {
185 if(isset($_GET['tags'])) {
186 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
188 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
189 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
191 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
192 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
196 if(isset($_GET['tags'])) {
197 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
198 $_SESSION['start_action'] = 'showp';
200 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
201 $_SESSION['current_photo'] = $_GET['id'];
202 $_SESSION['start_action'] = 'showp';
204 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
205 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
207 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
208 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
212 $this->tmpl->show("export.tpl");
216 $this->tmpl->show("slideshow.tpl");
220 if(isset($_GET['tags'])) {
221 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
223 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
224 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
226 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
227 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
235 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
236 $this->tmpl->assign('date_search_enabled', true);
238 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
239 $this->tmpl->assign('from_date', $this->get_calendar('from'));
240 $this->tmpl->assign('to_date', $this->get_calendar('to'));
241 $this->tmpl->assign('content_page', 'welcome.tpl');
242 $this->tmpl->show("index.tpl");
247 * get_tags - grab all tags of f-spot's database
249 * this function will get all available tags from
250 * the f-spot database and store them within two
251 * arrays within this class for later usage. in
252 * fact, if the user requests (hide_tags) it will
253 * opt-out some of them.
255 * this function is getting called once by show()
257 private function get_tags()
259 $this->avail_tags = Array();
262 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
265 DISTINCT t1.id as id, t1.name as name
268 INNER JOIN photo_tags
269 pt2 ON pt1.photo_id=pt2.photo_id
275 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
277 t1.sort_priority ASC";
279 $result = $this->db->db_query($query_str);
283 $result = $this->db->db_query("
286 ORDER BY sort_priority ASC
290 while($row = $this->db->db_fetch_object($result)) {
292 $tag_id = $row['id'];
293 $tag_name = $row['name'];
295 /* if the user has specified to ignore this tag in phpfspot's
296 configuration, ignore it here so it does not get added to
299 if(in_array($row['name'], $this->cfg->hide_tags))
302 /* if you include the following if-clause and the user has specified
303 to only show certain tags which are specified in phpfspot's
304 configuration, ignore all others so they will not be added to the
306 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
307 !in_array($row['name'], $this->cfg->show_tags))
311 $this->tags[$tag_id] = $tag_name;
312 $this->avail_tags[$count] = $tag_id;
320 * extract all photo details
322 * retrieve all available details from f-spot's
323 * database and return them as object
325 public function get_photo_details($idx)
327 if($this->dbver < 9) {
329 SELECT p.id, p.name, p.time, p.directory_path, p.description
335 SELECT p.id, p.uri, p.time, p.description
340 /* if show_tags is set, only return details for photos which
341 are specified to be shown
343 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
345 INNER JOIN photo_tags pt
349 WHERE p.id='". $idx ."'
350 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
354 WHERE p.id='". $idx ."'
358 if($result = $this->db->db_query($query_str)) {
360 $row = $this->db->db_fetch_object($result);
362 if($this->dbver < 9) {
363 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
372 } // get_photo_details
375 * returns aligned photo names
377 * this function returns aligned (length) names for
378 * an specific photo. If the length of the name exceeds
379 * $limit the name will be shrinked (...)
381 public function getPhotoName($idx, $limit = 0)
383 if($details = $this->get_photo_details($idx)) {
384 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
385 $name = $this->shrink_text($long_name, $limit);
395 * shrink text according provided limit
397 * If the length of the name exceeds $limit the
398 * text will be shortend and some content in between
399 * will be replaced with "..."
401 private function shrink_text($text, $limit)
403 if($limit != 0 && strlen($text) > $limit) {
404 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
412 * translate f-spoth photo path
414 * as the full-qualified path recorded in the f-spot database
415 * is usally not the same as on the webserver, this function
416 * will replace the path with that one specified in the cfg
418 public function translate_path($path, $width = 0)
420 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
425 * control HTML ouput for a single photo
427 * this function provides all the necessary information
428 * for the single photo template.
430 public function showPhoto($photo)
432 /* get all photos from the current photo selection */
433 $all_photos = $this->getPhotoSelection();
434 $count = count($all_photos);
436 for($i = 0; $i < $count; $i++) {
438 // $get_next will be set, when the photo which has to
439 // be displayed has been found - this means that the
440 // next available is in fact the NEXT image (for the
442 if(isset($get_next)) {
443 $next_img = $all_photos[$i];
447 /* the next photo is our NEXT photo */
448 if($all_photos[$i] == $photo) {
452 $previous_img = $all_photos[$i];
455 if($photo == $all_photos[$i]) {
460 $details = $this->get_photo_details($photo);
467 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
468 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
470 if(!file_exists($orig_path)) {
471 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
475 if(!is_readable($orig_path)) {
476 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
480 /* If the thumbnail doesn't exist yet, try to create it */
481 if(!file_exists($thumb_path)) {
482 $this->gen_thumb($photo, true);
483 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
486 /* get EXIF information if JPEG */
487 if($details['mime'] == "image/jpeg") {
488 $meta = $this->get_meta_informations($orig_path);
491 /* If EXIF data are available, use them */
492 if(isset($meta['ExifImageWidth'])) {
493 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
495 $info = getimagesize($orig_path);
496 $meta_res = $info[0] ."x". $info[1];
499 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
500 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
501 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
503 $extern_link = "index.php?mode=showp&id=". $photo;
504 $current_tags = $this->getCurrentTags();
505 if($current_tags != "") {
506 $extern_link.= "&tags=". $current_tags;
508 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
509 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
512 $this->tmpl->assign('extern_link', $extern_link);
514 if(!file_exists($thumb_path)) {
515 $this->_error("Can't open file ". $thumb_path ."\n");
519 $info = getimagesize($thumb_path);
521 $this->tmpl->assign('description', $details['description']);
522 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
524 $this->tmpl->assign('width', $info[0]);
525 $this->tmpl->assign('height', $info[1]);
526 $this->tmpl->assign('ExifMadeOn', $meta_date);
527 $this->tmpl->assign('ExifMadeWith', $meta_make);
528 $this->tmpl->assign('ExifOrigResolution', $meta_res);
529 $this->tmpl->assign('ExifFileSize', $meta_size);
531 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
532 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
533 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
535 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
536 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
537 $this->tmpl->assign('current_img', $photo);
540 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
541 $this->tmpl->assign('prev_img', $previous_img);
545 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
546 $this->tmpl->assign('next_img', $next_img);
548 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
549 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
550 $this->tmpl->assign('photo_number', $i);
551 $this->tmpl->assign('photo_count', count($all_photos));
553 $this->tmpl->show("single_photo.tpl");
558 * all available tags and tag cloud
560 * this function outputs all available tags (time ordered)
561 * and in addition output them as tag cloud (tags which have
562 * many photos will appears more then others)
564 public function getAvailableTags()
566 /* retrive tags from database */
571 $result = $this->db->db_query("
572 SELECT tag_id as id, count(tag_id) as quantity
582 while($row = $this->db->db_fetch_object($result)) {
583 $tags[$row['id']] = $row['quantity'];
586 // change these font sizes if you will
587 $max_size = 125; // max font size in %
588 $min_size = 75; // min font size in %
591 $max_sat = hexdec('cc');
592 $min_sat = hexdec('44');
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);
607 $step_sat = ($max_sat - $min_sat)/($spread);
609 // loop through our tag array
610 foreach ($tags as $key => $value) {
612 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
615 // calculate CSS font-size
616 // find the $value in excess of $min_qty
617 // multiply by the font-size increment ($size)
618 // and add the $min_size set above
619 $size = $min_size + (($value - $min_qty) * $step);
620 // uncomment if you want sizes in whole %:
623 $color = $min_sat + ($value - $min_qty) * $step_sat;
629 if(isset($this->tags[$key])) {
630 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
635 $output = substr($output, 0, strlen($output)-2);
638 } // getAvailableTags()
641 * output all selected tags
643 * this function output all tags which have been selected
644 * by the user. the selected tags are stored in the
645 * session-variable $_SESSION['selected_tags']
647 public function getSelectedTags()
649 /* retrive tags from database */
654 foreach($this->avail_tags as $tag)
656 // return all selected tags
657 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
658 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
663 $output = substr($output, 0, strlen($output)-2);
667 return "no tags selected";
670 } // getSelectedTags()
673 * add tag to users session variable
675 * this function will add the specified to users current
676 * tag selection. if a date search has been made before
677 * it will be now cleared
679 public function addTag($tag)
681 if(!isset($_SESSION['selected_tags']))
682 $_SESSION['selected_tags'] = Array();
684 if(isset($_SESSION['searchfor_tag']))
685 unset($_SESSION['searchfor_tag']);
687 if(!in_array($tag, $_SESSION['selected_tags']))
688 array_push($_SESSION['selected_tags'], $tag);
696 * remove tag to users session variable
698 * this function removes the specified tag from
699 * users current tag selection
701 public function delTag($tag)
703 if(isset($_SESSION['searchfor_tag']))
704 unset($_SESSION['searchfor_tag']);
706 if(isset($_SESSION['selected_tags'])) {
707 $key = array_search($tag, $_SESSION['selected_tags']);
708 unset($_SESSION['selected_tags'][$key]);
709 sort($_SESSION['selected_tags']);
717 * reset tag selection
719 * if there is any tag selection, it will be
722 public function resetTags()
724 if(isset($_SESSION['selected_tags']))
725 unset($_SESSION['selected_tags']);
730 * returns the value for the autocomplet tag-search
732 public function get_xml_tag_list()
734 if(!isset($_GET['search']) || !is_string($_GET['search']))
735 $_GET['search'] = '';
740 /* retrive tags from database */
743 $matched_tags = Array();
745 header("Content-Type: text/xml");
747 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
748 $string.= "<results>\n";
750 foreach($this->avail_tags as $tag)
752 if(!empty($_GET['search']) &&
753 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
754 count($matched_tags) < $length) {
756 $count = $this->get_num_photos($tag);
759 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
762 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
768 /* if we have collected enough items, break out */
769 if(count($matched_tags) >= $length)
773 $string.= "</results>\n";
777 } // get_xml_tag_list()
783 * if a specific photo was requested (external link)
784 * unset the session variable now
786 public function resetPhotoView()
788 if(isset($_SESSION['current_photo']))
789 unset($_SESSION['current_photo']);
791 } // resetPhotoView();
796 * if any tag search has taken place, reset it now
798 public function resetTagSearch()
800 if(isset($_SESSION['searchfor_tag']))
801 unset($_SESSION['searchfor_tag']);
803 } // resetTagSearch()
808 * if any name search has taken place, reset it now
810 public function resetNameSearch()
812 if(isset($_SESSION['searchfor_name']))
813 unset($_SESSION['searchfor_name']);
815 } // resetNameSearch()
820 * if any date search has taken place, reset
823 public function resetDateSearch()
825 if(isset($_SESSION['from_date']))
826 unset($_SESSION['from_date']);
827 if(isset($_SESSION['to_date']))
828 unset($_SESSION['to_date']);
830 } // resetDateSearch();
833 * return all photo according selection
835 * this function returns all photos based on
836 * the tag-selection, tag- or date-search.
837 * the tag-search also has to take care of AND
838 * and OR conjunctions
840 public function getPhotoSelection()
842 $matched_photos = Array();
843 $additional_where_cond = "";
845 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
846 $from_date = $_SESSION['from_date'];
847 $to_date = $_SESSION['to_date'];
848 $additional_where_cond.= "
849 p.time>='". $from_date ."'
851 p.time<='". $to_date ."'
855 if(isset($_SESSION['searchfor_name'])) {
856 if($this->dbver < 9) {
857 $additional_where_cond.= "
859 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
861 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
866 $additional_where_cond.= "
868 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
870 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
876 if(isset($_SESSION['sort_order'])) {
877 $order_str = $this->get_sort_order();
880 /* return a search result */
881 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
883 SELECT DISTINCT pt1.photo_id
885 INNER JOIN photo_tags pt2
886 ON pt1.photo_id=pt2.photo_id
893 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
895 if(isset($additional_where_cond) && !empty($additional_where_cond))
896 $query_str.= "AND ". $additional_where_cond ." ";
898 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
899 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
902 if(isset($order_str))
903 $query_str.= $order_str;
905 $result = $this->db->db_query($query_str);
906 while($row = $this->db->db_fetch_object($result)) {
907 array_push($matched_photos, $row['photo_id']);
909 return $matched_photos;
912 /* return according the selected tags */
913 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
915 foreach($_SESSION['selected_tags'] as $tag)
916 $selected.= $tag .",";
917 $selected = substr($selected, 0, strlen($selected)-1);
919 /* photo has to match at least on of the selected tags */
920 if($_SESSION['tag_condition'] == 'or') {
922 SELECT DISTINCT pt1.photo_id
924 INNER JOIN photo_tags pt2
925 ON pt1.photo_id=pt2.photo_id
930 WHERE pt1.tag_id IN (". $selected .")
932 if(isset($additional_where_cond) && !empty($additional_where_cond))
933 $query_str.= "AND ". $additional_where_cond ." ";
935 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
936 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
939 if(isset($order_str))
940 $query_str.= $order_str;
942 /* photo has to match all selected tags */
943 elseif($_SESSION['tag_condition'] == 'and') {
945 if(count($_SESSION['selected_tags']) >= 32) {
946 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
947 print "evaluate your tag selection. Please remove some tags from your selection.\n";
951 /* Join together a table looking like
953 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
955 so the query can quickly return all images matching the
956 selected tags in an AND condition
961 SELECT DISTINCT pt1.photo_id
965 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
972 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
974 INNER JOIN photo_tags pt". ($i+2) ."
975 ON pt1.photo_id=pt". ($i+2) .".photo_id
982 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
983 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
985 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
988 if(isset($additional_where_cond) && !empty($additional_where_cond))
989 $query_str.= "AND ". $additional_where_cond;
991 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
992 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
995 if(isset($order_str))
996 $query_str.= $order_str;
1000 $result = $this->db->db_query($query_str);
1001 while($row = $this->db->db_fetch_object($result)) {
1002 array_push($matched_photos, $row['photo_id']);
1004 return $matched_photos;
1007 /* return all available photos */
1009 SELECT DISTINCT p.id
1011 LEFT JOIN photo_tags pt
1017 if(isset($additional_where_cond) && !empty($additional_where_cond))
1018 $query_str.= "WHERE ". $additional_where_cond ." ";
1020 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1021 if(isset($additional_where_cond) && !empty($additional_where_cond))
1022 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1024 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1027 if(isset($order_str))
1028 $query_str.= $order_str;
1030 $result = $this->db->db_query($query_str);
1031 while($row = $this->db->db_fetch_object($result)) {
1032 array_push($matched_photos, $row['id']);
1034 return $matched_photos;
1036 } // getPhotoSelection()
1039 * control HTML ouput for photo index
1041 * this function provides all the necessary information
1042 * for the photo index template.
1044 public function showPhotoIndex()
1046 $photos = $this->getPhotoSelection();
1048 $count = count($photos);
1050 /* if all thumbnails should be shown on one page */
1051 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1055 /* thumbnails should be splitted up in several pages */
1056 elseif($this->cfg->thumbs_per_page > 0) {
1058 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1062 $begin_with = $_SESSION['begin_with'];
1065 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1069 $images[$thumbs] = Array();
1070 $img_height[$thumbs] = Array();
1071 $img_width[$thumbs] = Array();
1072 $img_id[$thumbs] = Array();
1073 $img_name[$thumbs] = Array();
1074 $img_fullname[$thumbs] = Array();
1075 $img_title = Array();
1077 for($i = $begin_with; $i < $end_with; $i++) {
1079 if(isset($photos[$i])) {
1081 $images[$thumbs] = $photos[$i];
1082 $img_id[$thumbs] = $i;
1083 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1084 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1085 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1087 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1089 if(file_exists($thumb_path)) {
1090 $info = getimagesize($thumb_path);
1091 $img_width[$thumbs] = $info[0];
1092 $img_height[$thumbs] = $info[1];
1098 // +1 for for smarty's selection iteration
1101 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1102 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1104 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1105 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1106 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1109 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1110 $this->tmpl->assign('tag_result', 1);
1113 /* do we have to display the page selector ? */
1114 if($this->cfg->thumbs_per_page != 0) {
1118 /* calculate the page switchers */
1119 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1120 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1122 if($begin_with != 0)
1123 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1124 if($end_with < $count)
1125 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1127 $photo_per_page = $this->cfg->thumbs_per_page;
1128 $last_page = ceil($count / $photo_per_page);
1130 /* get the current selected page */
1131 if($begin_with == 0) {
1135 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1142 for($i = 1; $i <= $last_page; $i++) {
1144 if($current_page == $i)
1145 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1146 elseif($current_page-1 == $i || $current_page+1 == $i)
1147 $style = "style=\"font-size: 105%;\"";
1148 elseif(($current_page-5 >= $i) && ($i != 1) ||
1149 ($current_page+5 <= $i) && ($i != $last_page))
1150 $style = "style=\"font-size: 75%;\"";
1154 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1157 $select.= ">". $i ."</a> ";
1159 // until 9 pages we show the selector from 1-9
1160 if($last_page <= 9) {
1161 $page_select.= $select;
1164 if($i == 1 /* first page */ ||
1165 $i == $last_page /* last page */ ||
1166 $i == $current_page /* current page */ ||
1167 $i == ceil($last_page * 0.25) /* first quater */ ||
1168 $i == ceil($last_page * 0.5) /* half */ ||
1169 $i == ceil($last_page * 0.75) /* third quater */ ||
1170 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1171 (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 */ ||
1172 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1173 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1175 $page_select.= $select;
1183 $page_select.= "......... ";
1188 /* only show the page selector if we have more then one page */
1190 $this->tmpl->assign('page_selector', $page_select);
1194 $current_tags = $this->getCurrentTags();
1195 $extern_link = "index.php?mode=showpi";
1196 $rss_link = "index.php?mode=rss";
1197 if($current_tags != "") {
1198 $extern_link.= "&tags=". $current_tags;
1199 $rss_link.= "&tags=". $current_tags;
1201 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1202 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1203 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1206 $export_link = "index.php?mode=export";
1207 $slideshow_link = "index.php?mode=slideshow";
1209 $this->tmpl->assign('extern_link', $extern_link);
1210 $this->tmpl->assign('slideshow_link', $slideshow_link);
1211 $this->tmpl->assign('export_link', $export_link);
1212 $this->tmpl->assign('rss_link', $rss_link);
1213 $this->tmpl->assign('count', $count);
1214 $this->tmpl->assign('width', $this->cfg->thumb_width);
1215 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1216 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1217 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1218 $this->tmpl->assign('images', $images);
1219 $this->tmpl->assign('img_width', $img_width);
1220 $this->tmpl->assign('img_height', $img_height);
1221 $this->tmpl->assign('img_id', $img_id);
1222 $this->tmpl->assign('img_name', $img_name);
1223 $this->tmpl->assign('img_fullname', $img_fullname);
1224 $this->tmpl->assign('img_title', $img_title);
1225 $this->tmpl->assign('thumbs', $thumbs);
1227 $this->tmpl->show("photo_index.tpl");
1229 /* if we are returning to photo index from an photo-view,
1230 scroll the window to the last shown photo-thumbnail.
1231 after this, unset the last_photo session variable.
1233 if(isset($_SESSION['last_photo'])) {
1234 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1235 unset($_SESSION['last_photo']);
1238 } // showPhotoIndex()
1241 * show credit template
1243 public function showCredits()
1245 $this->tmpl->assign('version', $this->cfg->version);
1246 $this->tmpl->assign('product', $this->cfg->product);
1247 $this->tmpl->assign('db_version', $this->dbver);
1248 $this->tmpl->show("credits.tpl");
1253 * create_thumbnails for the requested width
1255 * this function creates image thumbnails of $orig_image
1256 * stored as $thumb_image. It will check if the image is
1257 * in a supported format, if necessary rotate the image
1258 * (based on EXIF orientation meta headers) and re-sizing.
1260 public function create_thumbnail($orig_image, $thumb_image, $width)
1262 if(!file_exists($orig_image)) {
1266 $details = getimagesize($orig_image);
1268 /* check if original photo is a support image type */
1269 if(!$this->checkifImageSupported($details['mime']))
1272 switch($details['mime']) {
1276 $meta = $this->get_meta_informations($orig_image);
1282 switch($meta['Orientation']) {
1283 case 1: /* top, left */
1284 /* nothing to do */ break;
1285 case 2: /* top, right */
1286 $rotate = 0; $flip_hori = true; break;
1287 case 3: /* bottom, left */
1288 $rotate = 180; break;
1289 case 4: /* bottom, right */
1290 $flip_vert = true; break;
1291 case 5: /* left side, top */
1292 $rotate = 90; $flip_vert = true; break;
1293 case 6: /* right side, top */
1294 $rotate = 90; break;
1295 case 7: /* left side, bottom */
1296 $rotate = 270; $flip_vert = true; break;
1297 case 8: /* right side, bottom */
1298 $rotate = 270; break;
1301 $src_img = @imagecreatefromjpeg($orig_image);
1306 $src_img = @imagecreatefrompng($orig_image);
1312 print "Can't load image from ". $orig_image ."\n";
1316 /* grabs the height and width */
1317 $cur_width = imagesx($src_img);
1318 $cur_height = imagesy($src_img);
1320 // If requested width is more then the actual image width,
1321 // do not generate a thumbnail, instead safe the original
1322 // as thumbnail but with lower quality. But if the image
1323 // is to heigh too, then we still have to resize it.
1324 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1325 $result = imagejpeg($src_img, $thumb_image, 75);
1326 imagedestroy($src_img);
1330 // If the image will be rotate because EXIF orientation said so
1331 // 'virtually rotate' the image for further calculations
1332 if($rotate == 90 || $rotate == 270) {
1334 $cur_width = $cur_height;
1338 /* calculates aspect ratio */
1339 $aspect_ratio = $cur_height / $cur_width;
1342 if($aspect_ratio < 1) {
1344 $new_h = abs($new_w * $aspect_ratio);
1346 /* 'virtually' rotate the image and calculate it's ratio */
1347 $tmp_w = $cur_height;
1348 $tmp_h = $cur_width;
1349 /* now get the ratio from the 'rotated' image */
1350 $tmp_ratio = $tmp_h/$tmp_w;
1351 /* now calculate the new dimensions */
1353 $tmp_h = abs($tmp_w * $tmp_ratio);
1355 // now that we know, how high they photo should be, if it
1356 // gets rotated, use this high to scale the image
1358 $new_w = abs($new_h / $aspect_ratio);
1360 // If the image will be rotate because EXIF orientation said so
1361 // now 'virtually rotate' back the image for the image manipulation
1362 if($rotate == 90 || $rotate == 270) {
1369 /* creates new image of that size */
1370 $dst_img = imagecreatetruecolor($new_w, $new_h);
1372 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1374 /* copies resized portion of original image into new image */
1375 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1377 /* needs the image to be flipped horizontal? */
1379 $this->_debug("(FLIP)");
1380 $dst_img = $this->flipImage($dst_img, 'hori');
1382 /* needs the image to be flipped vertical? */
1384 $this->_debug("(FLIP)");
1385 $dst_img = $this->flipImage($dst_img, 'vert');
1389 $this->_debug("(ROTATE)");
1390 $dst_img = $this->rotateImage($dst_img, $rotate);
1393 /* write down new generated file */
1394 $result = imagejpeg($dst_img, $thumb_image, 75);
1396 /* free your mind */
1397 imagedestroy($dst_img);
1398 imagedestroy($src_img);
1400 if($result === false) {
1401 print "Can't write thumbnail ". $thumb_image ."\n";
1407 } // create_thumbnail()
1410 * return all exif meta data from the file
1412 public function get_meta_informations($file)
1414 return exif_read_data($file);
1416 } // get_meta_informations()
1419 * create phpfspot own sqlite database
1421 * this function creates phpfspots own sqlite database
1422 * if it does not exist yet. this own is used to store
1423 * some necessary informations (md5 sum's, ...).
1425 public function check_config_table()
1427 // if the config table doesn't exist yet, create it
1428 if(!$this->cfg_db->db_check_table_exists("images")) {
1429 $this->cfg_db->db_exec("
1430 CREATE TABLE images (
1431 img_idx int primary key,
1437 } // check_config_table
1440 * Generates a thumbnail from photo idx
1442 * This function will generate JPEG thumbnails from provided F-Spot photo
1445 * 1. Check if all thumbnail generations (width) are already in place and
1447 * 2. Check if the md5sum of the original file has changed
1448 * 3. Generate the thumbnails if needed
1450 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1454 $resolutions = Array(
1455 $this->cfg->thumb_width,
1456 $this->cfg->photo_width,
1457 $this->cfg->mini_width,
1460 /* get details from F-Spot's database */
1461 $details = $this->get_photo_details($idx);
1463 /* calculate file MD5 sum */
1464 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1466 if(!file_exists($full_path)) {
1467 $this->_error("File ". $full_path ." does not exist\n");
1471 if(!is_readable($full_path)) {
1472 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1476 $file_md5 = md5_file($full_path);
1478 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1482 foreach($resolutions as $resolution) {
1484 $generate_it = false;
1486 $thumb_sub_path = substr($file_md5, 0, 2);
1487 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1489 /* if thumbnail-subdirectory does not exist yet, create it */
1490 if(!file_exists(dirname($thumb_path))) {
1491 mkdir(dirname($thumb_path), 0755);
1494 /* if the thumbnail file doesn't exist, create it */
1495 if(!file_exists($thumb_path)) {
1496 $generate_it = true;
1498 /* if the file hasn't changed there is no need to regen the thumb */
1499 elseif($file_md5 != $this->getMD5($idx) || $force) {
1500 $generate_it = true;
1503 if($generate_it || $overwrite) {
1505 $this->_debug(" ". $resolution ."px");
1506 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1514 $this->_debug(" already exist");
1517 /* set the new/changed MD5 sum for the current photo */
1519 $this->setMD5($idx, $file_md5);
1522 $this->_debug("\n");
1527 * returns stored md5 sum for a specific photo
1529 * this function queries the phpfspot database for a
1530 * stored MD5 checksum of the specified photo
1532 public function getMD5($idx)
1534 $result = $this->cfg_db->db_query("
1537 WHERE img_idx='". $idx ."'
1543 $img = $this->cfg_db->db_fetch_object($result);
1544 return $img['img_md5'];
1549 * set MD5 sum for the specific photo
1551 private function setMD5($idx, $md5)
1553 $result = $this->cfg_db->db_exec("
1554 REPLACE INTO images (img_idx, img_md5)
1555 VALUES ('". $idx ."', '". $md5 ."')
1561 * store current tag condition
1563 * this function stores the current tag condition
1564 * (AND or OR) in the users session variables
1566 public function setTagCondition($mode)
1568 $_SESSION['tag_condition'] = $mode;
1572 } // setTagCondition()
1575 * invoke tag & date search
1577 * this function will return all matching tags and store
1578 * them in the session variable selected_tags. furthermore
1579 * it also handles the date search.
1580 * getPhotoSelection() will then only return the matching
1583 public function startSearch()
1585 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1586 $from = $_POST['from'];
1588 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1592 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1593 $searchfor_tag = $_POST['for_tag'];
1594 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1597 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1598 $searchfor_name = $_POST['for_name'];
1599 $_SESSION['searchfor_name'] = $_POST['for_name'];
1604 if(isset($from) && !empty($from))
1605 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1607 unset($_SESSION['from_date']);
1609 if(isset($to) && !empty($to))
1610 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1612 unset($_SESSION['to_date']);
1614 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1615 /* new search, reset the current selected tags */
1616 $_SESSION['selected_tags'] = Array();
1617 foreach($this->avail_tags as $tag) {
1618 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1619 array_push($_SESSION['selected_tags'], $tag);
1628 * updates sort order in session variable
1630 * this function is invoked by RPC and will sort the requested
1631 * sort order in the session variable.
1633 public function updateSortOrder($order)
1635 if(isset($this->sort_orders[$order])) {
1636 $_SESSION['sort_order'] = $order;
1640 return "unkown error";
1642 } // updateSortOrder()
1647 * this function rotates the image according the
1650 private function rotateImage($img, $degrees)
1652 if(function_exists("imagerotate")) {
1653 $img = imagerotate($img, $degrees, 0);
1655 function imagerotate($src_img, $angle)
1657 $src_x = imagesx($src_img);
1658 $src_y = imagesy($src_img);
1659 if ($angle == 180) {
1663 elseif ($src_x <= $src_y) {
1667 elseif ($src_x >= $src_y) {
1672 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1673 imagealphablending($rotate, false);
1678 for ($y = 0; $y < ($src_y); $y++) {
1679 for ($x = 0; $x < ($src_x); $x++) {
1680 $color = imagecolorat($src_img, $x, $y);
1681 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1687 for ($y = 0; $y < ($src_y); $y++) {
1688 for ($x = 0; $x < ($src_x); $x++) {
1689 $color = imagecolorat($src_img, $x, $y);
1690 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1696 for ($y = 0; $y < ($src_y); $y++) {
1697 for ($x = 0; $x < ($src_x); $x++) {
1698 $color = imagecolorat($src_img, $x, $y);
1699 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1713 $img = imagerotate($img, $degrees);
1722 * returns flipped image
1724 * this function will return an either horizontal or
1725 * vertical flipped truecolor image.
1727 private function flipImage($image, $mode)
1729 $w = imagesx($image);
1730 $h = imagesy($image);
1731 $flipped = imagecreatetruecolor($w, $h);
1735 for ($y = 0; $y < $h; $y++) {
1736 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1740 for ($x = 0; $x < $w; $x++) {
1741 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1751 * return all assigned tags for the specified photo
1753 private function get_photo_tags($idx)
1755 $result = $this->db->db_query("
1758 INNER JOIN photo_tags pt
1760 WHERE pt.photo_id='". $idx ."'
1765 while($row = $this->db->db_fetch_object($result))
1766 $tags[$row['id']] = $row['name'];
1770 } // get_photo_tags()
1773 * create on-the-fly images with text within
1775 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1777 if (strlen($color) != 6)
1780 $int = hexdec($color);
1781 $h = imagefontheight($font);
1782 $fw = imagefontwidth($font);
1783 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1784 $lines = count($txt);
1785 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1786 $bg = imagecolorallocate($im, 255, 255, 255);
1787 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1790 foreach ($txt as $text) {
1791 $x = (($w - ($fw * strlen($text))) / 2);
1792 imagestring($im, $font, $x, $y, $text, $color);
1793 $y += ($h + $space);
1796 Header("Content-type: image/png");
1799 } // showTextImage()
1802 * check if all requirements are met
1804 private function check_requirements()
1806 if(!function_exists("imagecreatefromjpeg")) {
1807 print "PHP GD library extension is missing<br />\n";
1811 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1812 print "PHP SQLite3 library extension is missing<br />\n";
1816 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1817 ini_set('track_errors', 1);
1818 @include_once 'HTML/AJAX/Server.php';
1819 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1820 print "PEAR HTML_AJAX package is missing<br />\n";
1823 @include_once 'Calendar/Calendar.php';
1824 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1825 print "PEAR Calendar package is missing<br />\n";
1828 @include_once 'Console/Getopt.php';
1829 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1830 print "PEAR Console_Getopt package is missing<br />\n";
1833 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1834 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1835 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1838 ini_restore('track_errors');
1845 } // check_requirements()
1847 private function _debug($text)
1849 if($this->fromcmd) {
1856 * check if specified MIME type is supported
1858 public function checkifImageSupported($mime)
1860 if(in_array($mime, Array("image/jpeg", "image/png")))
1865 } // checkifImageSupported()
1867 public function _error($text)
1869 switch($this->cfg->logging) {
1872 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1873 print $text ."<br />\n";
1879 error_log($text, 3, $his->cfg->log_file);
1883 $this->runtime_error = true;
1888 * output calendard input fields
1890 private function get_calendar($mode)
1892 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1893 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1894 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1896 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1897 if(!isset($_SESSION[$mode .'_date']))
1898 $output.= " disabled=\"disabled\"";
1900 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1901 if(!isset($_SESSION[$mode .'_date']))
1902 $output.= " disabled=\"disabled\"";
1904 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1905 if(!isset($_SESSION[$mode .'_date']))
1906 $output.= " disabled=\"disabled\"";
1914 * output calendar matrix
1916 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1918 if (!isset($year)) $year = date('Y');
1919 if (!isset($month)) $month = date('m');
1920 if (!isset($day)) $day = date('d');
1925 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1926 require_once CALENDAR_ROOT.'Day.php';
1929 $month = new Calendar_Month_Weekdays($year,$month);
1932 $prevStamp = $month->prevMonth(true);
1933 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1934 $nextStamp = $month->nextMonth(true);
1935 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1937 $selectedDays = array (
1938 new Calendar_Day($year,$month,$day),
1939 new Calendar_Day($year,12,25),
1942 // Build the days in the month
1943 $month->build($selectedDays);
1945 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1946 $this->tmpl->assign('prev_month', $prev);
1947 $this->tmpl->assign('next_month', $next);
1949 while ( $day = $month->fetch() ) {
1951 if(!isset($matrix[$rows]))
1952 $matrix[$rows] = Array();
1956 $dayStamp = $day->thisDay(true);
1957 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1959 // isFirst() to find start of week
1960 if ( $day->isFirst() )
1963 if ( $day->isSelected() ) {
1964 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1965 } else if ( $day->isEmpty() ) {
1966 $string.= "<td> </td>\n";
1968 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1971 // isLast() to find end of week
1972 if ( $day->isLast() )
1973 $string.= "</tr>\n";
1975 $matrix[$rows][$cols] = $string;
1985 $this->tmpl->assign('matrix', $matrix);
1986 $this->tmpl->assign('rows', $rows);
1987 $this->tmpl->show("calendar.tpl");
1989 } // get_calendar_matrix()
1992 * output export page
1994 public function getExport($mode)
1996 $pictures = $this->getPhotoSelection();
1997 $current_tags = $this->getCurrentTags();
1999 foreach($pictures as $picture) {
2001 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2002 if($current_tags != "") {
2003 $orig_url.= "&tags=". $current_tags;
2005 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2006 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2009 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2014 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2015 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2019 // "[%pictureurl% %thumbnailurl%]"
2020 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2023 case 'MoinMoinList':
2024 // " * [%pictureurl% %thumbnailurl%]"
2025 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2036 public function getRSSFeed()
2038 Header("Content-type: text/xml; charset=utf-8");
2039 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2042 xmlns:media="http://search.yahoo.com/mrss/"
2043 xmlns:dc="http://purl.org/dc/elements/1.1/"
2046 <title>phpfspot</title>
2047 <description>phpfspot RSS feed</description>
2048 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2049 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2050 <generator>phpfspot</generator>
2053 $pictures = $this->getPhotoSelection();
2054 $current_tags = $this->getCurrentTags();
2056 foreach($pictures as $picture) {
2058 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2059 if($current_tags != "") {
2060 $orig_url.= "&tags=". $current_tags;
2062 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2063 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2066 $details = $this->get_photo_details($picture);
2068 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2069 $thumb_html = htmlspecialchars("
2070 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2072 ". $details['description']);
2074 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2076 /* get EXIF information if JPEG */
2077 if($details['mime'] == "image/jpeg") {
2078 $meta = $this->get_meta_informations($orig_path);
2081 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2085 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2086 <link><?php print htmlspecialchars($orig_url); ?></link>
2087 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2088 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2090 <?php print $thumb_html; ?>
2092 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2107 * return all selected tags as one string
2109 private function getCurrentTags()
2112 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2113 foreach($_SESSION['selected_tags'] as $tag)
2114 $current_tags.= $tag .",";
2115 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2117 return $current_tags;
2119 } // getCurrentTags()
2122 * return the current photo
2124 public function getCurrentPhoto()
2126 if(isset($_SESSION['current_photo'])) {
2127 print $_SESSION['current_photo'];
2129 } // getCurrentPhoto()
2132 * tells the client browser what to do
2134 * this function is getting called via AJAX by the
2135 * client browsers. it will tell them what they have
2136 * to do next. This is necessary for directly jumping
2137 * into photo index or single photo view when the are
2138 * requested with specific URLs
2140 public function whatToDo()
2142 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2143 return "show_photo";
2145 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2146 return "showpi_tags";
2148 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2152 return "nothing special";
2157 * return the current process-user
2159 private function getuid()
2161 if($uid = posix_getuid()) {
2162 if($user = posix_getpwuid($uid)) {
2163 return $user['name'];
2172 * returns a select-dropdown box to select photo index sort parameters
2174 public function smarty_sort_select_list($params, &$smarty)
2178 foreach($this->sort_orders as $key => $value) {
2179 $output.= "<option value=\"". $key ."\"";
2180 if($key == $_SESSION['sort_order']) {
2181 $output.= " selected=\"selected\"";
2183 $output.= ">". $value ."</option>";
2188 } // smarty_sort_select_list()
2191 * returns the currently selected sort order
2193 private function get_sort_order()
2195 switch($_SESSION['sort_order']) {
2197 return " ORDER BY p.time ASC";
2200 return " ORDER BY p.time DESC";
2203 if($this->dbver < 9) {
2204 return " ORDER BY p.name ASC";
2207 return " ORDER BY basename(p.uri) ASC";
2211 if($this->dbver < 9) {
2212 return " ORDER BY p.name DESC";
2215 return " ORDER BY basename(p.uri) DESC";
2219 return " ORDER BY t.name ASC ,p.time ASC";
2222 return " ORDER BY t.name DESC ,p.time ASC";
2226 } // get_sort_order()
2229 * return the next to be shown slide show image
2231 * this function returns the URL of the next image
2232 * in the slideshow sequence.
2234 public function getNextSlideShowImage()
2236 $all_photos = $this->getPhotoSelection();
2238 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2239 $_SESSION['slideshow_img'] = 0;
2241 $_SESSION['slideshow_img']++;
2243 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2245 } // getNextSlideShowImage()
2248 * return the previous to be shown slide show image
2250 * this function returns the URL of the previous image
2251 * in the slideshow sequence.
2253 public function getPrevSlideShowImage()
2255 $all_photos = $this->getPhotoSelection();
2257 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2258 $_SESSION['slideshow_img'] = 0;
2260 $_SESSION['slideshow_img']--;
2262 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2264 } // getPrevSlideShowImage()
2266 public function resetSlideShow()
2268 if(isset($_SESSION['slideshow_img']))
2269 unset($_SESSION['slideshow_img']);
2271 } // resetSlideShow()
2276 * this function will get all photos from the fspot
2277 * database and randomly return ONE entry
2279 * saddly there is yet no sqlite3 function which returns
2280 * the bulk result in array, so we have to fill up our
2283 public function get_random_photo()
2287 $result = $this->db->db_query("
2292 while($row = $this->db->db_fetch_object($result)) {
2293 array_push($all, $row['id']);
2296 return $all[array_rand($all)];
2298 } // get_random_photo()
2301 * validates provided date
2303 * this function validates if the provided date
2304 * contains a valid date and will return true
2307 public function isValidDate($date_str)
2309 $timestamp = strtotime($date_str);
2311 if(is_numeric($timestamp))
2319 * timestamp to string conversion
2321 private function ts2str($timestamp)
2323 return strftime("%Y-%m-%d", $timestamp);
2326 private function extractTags($tags_str)
2328 $not_validated = split(',', $_GET['tags']);
2329 $validated = array();
2331 foreach($not_validated as $tag) {
2332 if(is_numeric($tag))
2333 array_push($validated, $tag);
2341 * returns the full path to a thumbnail
2343 public function get_thumb_path($width, $photo)
2345 $md5 = $this->getMD5($photo);
2346 $sub_path = substr($md5, 0, 2);
2347 return $this->cfg->thumb_path
2355 } // get_thumb_path()
2358 * returns server's virtual host name
2360 private function get_server_name()
2362 return $_SERVER['SERVER_NAME'];
2363 } // get_server_name()
2366 * returns type of webprotocol which is
2369 private function get_web_protocol()
2371 if(!isset($_SERVER['HTTPS']))
2375 } // get_web_protocol()
2378 * return url to this phpfspot installation
2380 private function get_phpfspot_url()
2382 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2383 } // get_phpfspot_url()
2386 * returns the number of photos which are tagged with $tag_id
2388 public function get_num_photos($tag_id)
2390 if($result = $this->db->db_fetchSingleRow("
2391 SELECT count(*) as number
2394 tag_id LIKE '". $tag_id ."'")) {
2396 return $result['number'];
2402 } // get_num_photos()
2405 * check file exists and is readable
2407 * returns true, if everything is ok, otherwise false
2408 * if $silent is not set, this function will output and
2411 private function check_readable($file, $silent = null)
2413 if(!file_exists($file)) {
2415 print "File \"". $file ."\" does not exist.\n";
2419 if(!is_readable($file)) {
2421 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2427 } // check_readable()
2430 * check if all needed indices are present
2432 * this function checks, if some needed indices are already
2433 * present, or if not, create them on the fly. they are
2434 * necessary to speed up some queries like that one look for
2435 * all tags, when show_tags is specified in the configuration.
2437 private function checkDbIndices()
2439 $result = $this->db->db_exec("
2440 CREATE INDEX IF NOT EXISTS
2447 } // checkDbIndices()
2450 * retrive F-Spot database version
2452 * this function will return the F-Spot database version number
2453 * It is stored within the sqlite3 database in the table meta
2455 public function getFspotDBVersion()
2457 if($result = $this->db->db_fetchSingleRow("
2458 SELECT data as version
2461 name LIKE 'F-Spot Database Version'
2463 return $result['version'];
2467 } // getFspotDBVersion()
2470 * parse the provided URI and will returned the
2473 public function parse_uri($uri, $mode)
2475 if(($components = parse_url($uri)) !== false) {
2479 return basename($components['path']);
2482 return dirname($components['path']);
2485 return $components['path'];
2495 * validate config options
2497 * this function checks if all necessary configuration options are
2498 * specified and set.
2500 private function check_config_options()
2502 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2503 $this->_error("Please set \$page_title in phpfspot_cfg");
2505 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2506 $this->_error("Please set \$base_path in phpfspot_cfg");
2508 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2509 $this->_error("Please set \$web_path in phpfspot_cfg");
2511 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2512 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2514 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2515 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2517 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2518 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2520 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2521 $this->_error("Please set \$db_access in phpfspot_cfg");
2523 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2524 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2526 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2527 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2529 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2530 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2532 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2533 $this->_error("Please set \$photo_width in phpfspot_cfg");
2535 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2536 $this->_error("Please set \$mini_width in phpfspot_cfg");
2538 if(!isset($this->cfg->thumbs_per_page))
2539 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2541 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2542 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2544 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2545 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2547 if(!isset($this->cfg->hide_tags))
2548 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2550 if(!isset($this->cfg->theme_name))
2551 $this->_error("Please set \$theme_name in phpfspot_cfg");
2553 if(!isset($this->cfg->logging))
2554 $this->_error("Please set \$logging in phpfspot_cfg");
2556 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2558 if(!isset($this->cfg->log_file))
2559 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2561 if(!is_writeable($this->cfg->log_file))
2562 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2566 /* check for pending slash on web_path */
2567 if(!preg_match("/\/$/", $this->cfg->web_path))
2568 $this->cfg->web_path.= "/";
2570 return $this->runtime_error;
2572 } // check_config_options()
2575 * cleanup phpfspot own database
2577 * When photos are getting delete from F-Spot, there will remain
2578 * remain some residues in phpfspot own database. This function
2579 * will try to wipe them out.
2581 public function cleanup_phpfspot_db()
2583 $to_delete = Array();
2585 $result = $this->cfg_db->db_query("
2588 ORDER BY img_idx ASC
2591 while($row = $this->cfg_db->db_fetch_object($result)) {
2592 if(!$this->db->db_fetchSingleRow("
2595 WHERE id='". $row['img_idx'] ."'")) {
2597 array_push($to_delete, $row['img_idx'], ',');
2601 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2603 $this->cfg_db->db_exec("
2605 WHERE img_idx IN (". implode($to_delete) .")
2608 } // cleanup_phpfspot_db()
2611 * return first image of the page, the $current photo
2614 * this function is used to find out the first photo of the
2615 * current page, in which the $current photo lies. this is
2616 * used to display the correct photo, when calling showPhotoIndex()
2619 private function getCurrentPage($current, $max)
2621 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2622 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2623 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2629 } // getCurrentPage()