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_number', $i);
550 $this->tmpl->assign('photo_count', count($all_photos));
552 $this->tmpl->show("single_photo.tpl");
557 * all available tags and tag cloud
559 * this function outputs all available tags (time ordered)
560 * and in addition output them as tag cloud (tags which have
561 * many photos will appears more then others)
563 public function getAvailableTags()
565 /* retrive tags from database */
570 $result = $this->db->db_query("
571 SELECT tag_id as id, count(tag_id) as quantity
581 while($row = $this->db->db_fetch_object($result)) {
582 $tags[$row['id']] = $row['quantity'];
585 // change these font sizes if you will
586 $max_size = 125; // max font size in %
587 $min_size = 75; // min font size in %
589 // get the largest and smallest array values
590 $max_qty = max(array_values($tags));
591 $min_qty = min(array_values($tags));
593 // find the range of values
594 $spread = $max_qty - $min_qty;
595 if (0 == $spread) { // we don't want to divide by zero
599 // determine the font-size increment
600 // this is the increase per tag quantity (times used)
601 $step = ($max_size - $min_size)/($spread);
603 // loop through our tag array
604 foreach ($tags as $key => $value) {
606 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
609 // calculate CSS font-size
610 // find the $value in excess of $min_qty
611 // multiply by the font-size increment ($size)
612 // and add the $min_size set above
613 $size = $min_size + (($value - $min_qty) * $step);
614 // uncomment if you want sizes in whole %:
617 if(isset($this->tags[$key])) {
618 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
623 $output = substr($output, 0, strlen($output)-2);
626 } // getAvailableTags()
629 * output all selected tags
631 * this function output all tags which have been selected
632 * by the user. the selected tags are stored in the
633 * session-variable $_SESSION['selected_tags']
635 public function getSelectedTags()
637 /* retrive tags from database */
642 foreach($this->avail_tags as $tag)
644 // return all selected tags
645 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
646 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
651 $output = substr($output, 0, strlen($output)-2);
655 return "no tags selected";
658 } // getSelectedTags()
661 * add tag to users session variable
663 * this function will add the specified to users current
664 * tag selection. if a date search has been made before
665 * it will be now cleared
667 public function addTag($tag)
669 if(!isset($_SESSION['selected_tags']))
670 $_SESSION['selected_tags'] = Array();
672 if(isset($_SESSION['searchfor_tag']))
673 unset($_SESSION['searchfor_tag']);
675 if(!in_array($tag, $_SESSION['selected_tags']))
676 array_push($_SESSION['selected_tags'], $tag);
684 * remove tag to users session variable
686 * this function removes the specified tag from
687 * users current tag selection
689 public function delTag($tag)
691 if(isset($_SESSION['searchfor_tag']))
692 unset($_SESSION['searchfor_tag']);
694 if(isset($_SESSION['selected_tags'])) {
695 $key = array_search($tag, $_SESSION['selected_tags']);
696 unset($_SESSION['selected_tags'][$key]);
697 sort($_SESSION['selected_tags']);
705 * reset tag selection
707 * if there is any tag selection, it will be
710 public function resetTags()
712 if(isset($_SESSION['selected_tags']))
713 unset($_SESSION['selected_tags']);
718 * returns the value for the autocomplet tag-search
720 public function get_xml_tag_list()
722 if(!isset($_GET['search']) || !is_string($_GET['search']))
723 $_GET['search'] = '';
728 /* retrive tags from database */
731 $matched_tags = Array();
733 header("Content-Type: text/xml");
735 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
736 $string.= "<results>\n";
738 foreach($this->avail_tags as $tag)
740 if(!empty($_GET['search']) &&
741 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
742 count($matched_tags) < $length) {
744 $count = $this->get_num_photos($tag);
747 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
750 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
756 /* if we have collected enough items, break out */
757 if(count($matched_tags) >= $length)
761 $string.= "</results>\n";
765 } // get_xml_tag_list()
771 * if a specific photo was requested (external link)
772 * unset the session variable now
774 public function resetPhotoView()
776 if(isset($_SESSION['current_photo']))
777 unset($_SESSION['current_photo']);
779 } // resetPhotoView();
784 * if any tag search has taken place, reset it now
786 public function resetTagSearch()
788 if(isset($_SESSION['searchfor_tag']))
789 unset($_SESSION['searchfor_tag']);
791 } // resetTagSearch()
796 * if any name search has taken place, reset it now
798 public function resetNameSearch()
800 if(isset($_SESSION['searchfor_name']))
801 unset($_SESSION['searchfor_name']);
803 } // resetNameSearch()
808 * if any date search has taken place, reset
811 public function resetDateSearch()
813 if(isset($_SESSION['from_date']))
814 unset($_SESSION['from_date']);
815 if(isset($_SESSION['to_date']))
816 unset($_SESSION['to_date']);
818 } // resetDateSearch();
821 * return all photo according selection
823 * this function returns all photos based on
824 * the tag-selection, tag- or date-search.
825 * the tag-search also has to take care of AND
826 * and OR conjunctions
828 public function getPhotoSelection()
830 $matched_photos = Array();
831 $additional_where_cond = "";
833 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
834 $from_date = $_SESSION['from_date'];
835 $to_date = $_SESSION['to_date'];
836 $additional_where_cond.= "
837 p.time>='". $from_date ."'
839 p.time<='". $to_date ."'
843 if(isset($_SESSION['searchfor_name'])) {
844 if($this->dbver < 9) {
845 $additional_where_cond.= "
847 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
849 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
854 $additional_where_cond.= "
856 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
858 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
864 if(isset($_SESSION['sort_order'])) {
865 $order_str = $this->get_sort_order();
868 /* return a search result */
869 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
871 SELECT DISTINCT pt1.photo_id
873 INNER JOIN photo_tags pt2
874 ON pt1.photo_id=pt2.photo_id
881 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
883 if(isset($additional_where_cond) && !empty($additional_where_cond))
884 $query_str.= "AND ". $additional_where_cond ." ";
886 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
887 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
890 if(isset($order_str))
891 $query_str.= $order_str;
893 $result = $this->db->db_query($query_str);
894 while($row = $this->db->db_fetch_object($result)) {
895 array_push($matched_photos, $row['photo_id']);
897 return $matched_photos;
900 /* return according the selected tags */
901 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
903 foreach($_SESSION['selected_tags'] as $tag)
904 $selected.= $tag .",";
905 $selected = substr($selected, 0, strlen($selected)-1);
907 /* photo has to match at least on of the selected tags */
908 if($_SESSION['tag_condition'] == 'or') {
910 SELECT DISTINCT pt1.photo_id
912 INNER JOIN photo_tags pt2
913 ON pt1.photo_id=pt2.photo_id
918 WHERE pt1.tag_id IN (". $selected .")
920 if(isset($additional_where_cond) && !empty($additional_where_cond))
921 $query_str.= "AND ". $additional_where_cond ." ";
923 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
924 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
927 if(isset($order_str))
928 $query_str.= $order_str;
930 /* photo has to match all selected tags */
931 elseif($_SESSION['tag_condition'] == 'and') {
933 if(count($_SESSION['selected_tags']) >= 32) {
934 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
935 print "evaluate your tag selection. Please remove some tags from your selection.\n";
939 /* Join together a table looking like
941 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
943 so the query can quickly return all images matching the
944 selected tags in an AND condition
949 SELECT DISTINCT pt1.photo_id
953 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
960 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
962 INNER JOIN photo_tags pt". ($i+2) ."
963 ON pt1.photo_id=pt". ($i+2) .".photo_id
970 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
971 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
973 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
976 if(isset($additional_where_cond) && !empty($additional_where_cond))
977 $query_str.= "AND ". $additional_where_cond;
979 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
980 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
983 if(isset($order_str))
984 $query_str.= $order_str;
988 $result = $this->db->db_query($query_str);
989 while($row = $this->db->db_fetch_object($result)) {
990 array_push($matched_photos, $row['photo_id']);
992 return $matched_photos;
995 /* return all available photos */
999 LEFT JOIN photo_tags pt
1005 if(isset($additional_where_cond) && !empty($additional_where_cond))
1006 $query_str.= "WHERE ". $additional_where_cond ." ";
1008 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1009 if(isset($additional_where_cond) && !empty($additional_where_cond))
1010 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1012 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1015 if(isset($order_str))
1016 $query_str.= $order_str;
1018 $result = $this->db->db_query($query_str);
1019 while($row = $this->db->db_fetch_object($result)) {
1020 array_push($matched_photos, $row['id']);
1022 return $matched_photos;
1024 } // getPhotoSelection()
1027 * control HTML ouput for photo index
1029 * this function provides all the necessary information
1030 * for the photo index template.
1032 public function showPhotoIndex()
1034 $photos = $this->getPhotoSelection();
1036 $count = count($photos);
1038 /* if all thumbnails should be shown on one page */
1039 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1043 /* thumbnails should be splitted up in several pages */
1044 elseif($this->cfg->thumbs_per_page > 0) {
1046 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1050 $begin_with = $_SESSION['begin_with'];
1053 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1057 $images[$thumbs] = Array();
1058 $img_height[$thumbs] = Array();
1059 $img_width[$thumbs] = Array();
1060 $img_id[$thumbs] = Array();
1061 $img_name[$thumbs] = Array();
1062 $img_fullname[$thumbs] = Array();
1063 $img_title = Array();
1065 for($i = $begin_with; $i < $end_with; $i++) {
1067 if(isset($photos[$i])) {
1069 $images[$thumbs] = $photos[$i];
1070 $img_id[$thumbs] = $i;
1071 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1072 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1073 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1075 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1077 if(file_exists($thumb_path)) {
1078 $info = getimagesize($thumb_path);
1079 $img_width[$thumbs] = $info[0];
1080 $img_height[$thumbs] = $info[1];
1086 // +1 for for smarty's selection iteration
1089 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1090 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1092 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1093 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1094 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1097 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1098 $this->tmpl->assign('tag_result', 1);
1101 /* do we have to display the page selector ? */
1102 if($this->cfg->thumbs_per_page != 0) {
1106 /* calculate the page switchers */
1107 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1108 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1110 if($begin_with != 0)
1111 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1112 if($end_with < $count)
1113 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1115 $photo_per_page = $this->cfg->thumbs_per_page;
1116 $last_page = ceil($count / $photo_per_page);
1118 /* get the current selected page */
1119 if($begin_with == 0) {
1123 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1130 for($i = 1; $i <= $last_page; $i++) {
1132 if($current_page == $i)
1133 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1134 elseif($current_page-1 == $i || $current_page+1 == $i)
1135 $style = "style=\"font-size: 105%;\"";
1136 elseif(($current_page-5 >= $i) && ($i != 1) ||
1137 ($current_page+5 <= $i) && ($i != $last_page))
1138 $style = "style=\"font-size: 75%;\"";
1142 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1145 $select.= ">". $i ."</a> ";
1147 // until 9 pages we show the selector from 1-9
1148 if($last_page <= 9) {
1149 $page_select.= $select;
1152 if($i == 1 /* first page */ ||
1153 $i == $last_page /* last page */ ||
1154 $i == $current_page /* current page */ ||
1155 $i == ceil($last_page * 0.25) /* first quater */ ||
1156 $i == ceil($last_page * 0.5) /* half */ ||
1157 $i == ceil($last_page * 0.75) /* third quater */ ||
1158 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1159 (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 */ ||
1160 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1161 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1163 $page_select.= $select;
1171 $page_select.= "......... ";
1176 /* only show the page selector if we have more then one page */
1178 $this->tmpl->assign('page_selector', $page_select);
1182 $current_tags = $this->getCurrentTags();
1183 $extern_link = "index.php?mode=showpi";
1184 $rss_link = "index.php?mode=rss";
1185 if($current_tags != "") {
1186 $extern_link.= "&tags=". $current_tags;
1187 $rss_link.= "&tags=". $current_tags;
1189 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1190 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1191 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1194 $export_link = "index.php?mode=export";
1195 $slideshow_link = "index.php?mode=slideshow";
1197 $this->tmpl->assign('extern_link', $extern_link);
1198 $this->tmpl->assign('slideshow_link', $slideshow_link);
1199 $this->tmpl->assign('export_link', $export_link);
1200 $this->tmpl->assign('rss_link', $rss_link);
1201 $this->tmpl->assign('count', $count);
1202 $this->tmpl->assign('width', $this->cfg->thumb_width);
1203 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1204 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1205 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1206 $this->tmpl->assign('images', $images);
1207 $this->tmpl->assign('img_width', $img_width);
1208 $this->tmpl->assign('img_height', $img_height);
1209 $this->tmpl->assign('img_id', $img_id);
1210 $this->tmpl->assign('img_name', $img_name);
1211 $this->tmpl->assign('img_fullname', $img_fullname);
1212 $this->tmpl->assign('img_title', $img_title);
1213 $this->tmpl->assign('thumbs', $thumbs);
1215 $this->tmpl->show("photo_index.tpl");
1217 /* if we are returning to photo index from an photo-view,
1218 scroll the window to the last shown photo-thumbnail.
1219 after this, unset the last_photo session variable.
1221 if(isset($_SESSION['last_photo'])) {
1222 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1223 unset($_SESSION['last_photo']);
1226 } // showPhotoIndex()
1229 * show credit template
1231 public function showCredits()
1233 $this->tmpl->assign('version', $this->cfg->version);
1234 $this->tmpl->assign('product', $this->cfg->product);
1235 $this->tmpl->assign('db_version', $this->dbver);
1236 $this->tmpl->show("credits.tpl");
1241 * create_thumbnails for the requested width
1243 * this function creates image thumbnails of $orig_image
1244 * stored as $thumb_image. It will check if the image is
1245 * in a supported format, if necessary rotate the image
1246 * (based on EXIF orientation meta headers) and re-sizing.
1248 public function create_thumbnail($orig_image, $thumb_image, $width)
1250 if(!file_exists($orig_image)) {
1254 $details = getimagesize($orig_image);
1256 /* check if original photo is a support image type */
1257 if(!$this->checkifImageSupported($details['mime']))
1260 switch($details['mime']) {
1264 $meta = $this->get_meta_informations($orig_image);
1270 switch($meta['Orientation']) {
1271 case 1: /* top, left */
1272 /* nothing to do */ break;
1273 case 2: /* top, right */
1274 $rotate = 0; $flip_hori = true; break;
1275 case 3: /* bottom, left */
1276 $rotate = 180; break;
1277 case 4: /* bottom, right */
1278 $flip_vert = true; break;
1279 case 5: /* left side, top */
1280 $rotate = 90; $flip_vert = true; break;
1281 case 6: /* right side, top */
1282 $rotate = 90; break;
1283 case 7: /* left side, bottom */
1284 $rotate = 270; $flip_vert = true; break;
1285 case 8: /* right side, bottom */
1286 $rotate = 270; break;
1289 $src_img = @imagecreatefromjpeg($orig_image);
1294 $src_img = @imagecreatefrompng($orig_image);
1300 print "Can't load image from ". $orig_image ."\n";
1304 /* grabs the height and width */
1305 $cur_width = imagesx($src_img);
1306 $cur_height = imagesy($src_img);
1308 // If requested width is more then the actual image width,
1309 // do not generate a thumbnail, instead safe the original
1310 // as thumbnail but with lower quality. But if the image
1311 // is to heigh too, then we still have to resize it.
1312 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1313 $result = imagejpeg($src_img, $thumb_image, 75);
1314 imagedestroy($src_img);
1318 // If the image will be rotate because EXIF orientation said so
1319 // 'virtually rotate' the image for further calculations
1320 if($rotate == 90 || $rotate == 270) {
1322 $cur_width = $cur_height;
1326 /* calculates aspect ratio */
1327 $aspect_ratio = $cur_height / $cur_width;
1330 if($aspect_ratio < 1) {
1332 $new_h = abs($new_w * $aspect_ratio);
1334 /* 'virtually' rotate the image and calculate it's ratio */
1335 $tmp_w = $cur_height;
1336 $tmp_h = $cur_width;
1337 /* now get the ratio from the 'rotated' image */
1338 $tmp_ratio = $tmp_h/$tmp_w;
1339 /* now calculate the new dimensions */
1341 $tmp_h = abs($tmp_w * $tmp_ratio);
1343 // now that we know, how high they photo should be, if it
1344 // gets rotated, use this high to scale the image
1346 $new_w = abs($new_h / $aspect_ratio);
1348 // If the image will be rotate because EXIF orientation said so
1349 // now 'virtually rotate' back the image for the image manipulation
1350 if($rotate == 90 || $rotate == 270) {
1357 /* creates new image of that size */
1358 $dst_img = imagecreatetruecolor($new_w, $new_h);
1360 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1362 /* copies resized portion of original image into new image */
1363 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1365 /* needs the image to be flipped horizontal? */
1367 $this->_debug("(FLIP)");
1368 $dst_img = $this->flipImage($dst_img, 'hori');
1370 /* needs the image to be flipped vertical? */
1372 $this->_debug("(FLIP)");
1373 $dst_img = $this->flipImage($dst_img, 'vert');
1377 $this->_debug("(ROTATE)");
1378 $dst_img = $this->rotateImage($dst_img, $rotate);
1381 /* write down new generated file */
1382 $result = imagejpeg($dst_img, $thumb_image, 75);
1384 /* free your mind */
1385 imagedestroy($dst_img);
1386 imagedestroy($src_img);
1388 if($result === false) {
1389 print "Can't write thumbnail ". $thumb_image ."\n";
1395 } // create_thumbnail()
1398 * return all exif meta data from the file
1400 public function get_meta_informations($file)
1402 return exif_read_data($file);
1404 } // get_meta_informations()
1407 * create phpfspot own sqlite database
1409 * this function creates phpfspots own sqlite database
1410 * if it does not exist yet. this own is used to store
1411 * some necessary informations (md5 sum's, ...).
1413 public function check_config_table()
1415 // if the config table doesn't exist yet, create it
1416 if(!$this->cfg_db->db_check_table_exists("images")) {
1417 $this->cfg_db->db_exec("
1418 CREATE TABLE images (
1419 img_idx int primary key,
1425 } // check_config_table
1428 * Generates a thumbnail from photo idx
1430 * This function will generate JPEG thumbnails from provided F-Spot photo
1433 * 1. Check if all thumbnail generations (width) are already in place and
1435 * 2. Check if the md5sum of the original file has changed
1436 * 3. Generate the thumbnails if needed
1438 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1442 $resolutions = Array(
1443 $this->cfg->thumb_width,
1444 $this->cfg->photo_width,
1445 $this->cfg->mini_width,
1448 /* get details from F-Spot's database */
1449 $details = $this->get_photo_details($idx);
1451 /* calculate file MD5 sum */
1452 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1454 if(!file_exists($full_path)) {
1455 $this->_error("File ". $full_path ." does not exist\n");
1459 if(!is_readable($full_path)) {
1460 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1464 $file_md5 = md5_file($full_path);
1466 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1470 foreach($resolutions as $resolution) {
1472 $generate_it = false;
1474 $thumb_sub_path = substr($file_md5, 0, 2);
1475 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1477 /* if thumbnail-subdirectory does not exist yet, create it */
1478 if(!file_exists(dirname($thumb_path))) {
1479 mkdir(dirname($thumb_path), 0755);
1482 /* if the thumbnail file doesn't exist, create it */
1483 if(!file_exists($thumb_path)) {
1484 $generate_it = true;
1486 /* if the file hasn't changed there is no need to regen the thumb */
1487 elseif($file_md5 != $this->getMD5($idx) || $force) {
1488 $generate_it = true;
1491 if($generate_it || $overwrite) {
1493 $this->_debug(" ". $resolution ."px");
1494 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1502 $this->_debug(" already exist");
1505 /* set the new/changed MD5 sum for the current photo */
1507 $this->setMD5($idx, $file_md5);
1510 $this->_debug("\n");
1515 * returns stored md5 sum for a specific photo
1517 * this function queries the phpfspot database for a
1518 * stored MD5 checksum of the specified photo
1520 public function getMD5($idx)
1522 $result = $this->cfg_db->db_query("
1525 WHERE img_idx='". $idx ."'
1531 $img = $this->cfg_db->db_fetch_object($result);
1532 return $img['img_md5'];
1537 * set MD5 sum for the specific photo
1539 private function setMD5($idx, $md5)
1541 $result = $this->cfg_db->db_exec("
1542 REPLACE INTO images (img_idx, img_md5)
1543 VALUES ('". $idx ."', '". $md5 ."')
1549 * store current tag condition
1551 * this function stores the current tag condition
1552 * (AND or OR) in the users session variables
1554 public function setTagCondition($mode)
1556 $_SESSION['tag_condition'] = $mode;
1560 } // setTagCondition()
1563 * invoke tag & date search
1565 * this function will return all matching tags and store
1566 * them in the session variable selected_tags. furthermore
1567 * it also handles the date search.
1568 * getPhotoSelection() will then only return the matching
1571 public function startSearch()
1573 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1574 $from = $_POST['from'];
1576 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1580 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1581 $searchfor_tag = $_POST['for_tag'];
1582 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1585 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1586 $searchfor_name = $_POST['for_name'];
1587 $_SESSION['searchfor_name'] = $_POST['for_name'];
1592 if(isset($from) && !empty($from))
1593 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1595 unset($_SESSION['from_date']);
1597 if(isset($to) && !empty($to))
1598 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1600 unset($_SESSION['to_date']);
1602 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1603 /* new search, reset the current selected tags */
1604 $_SESSION['selected_tags'] = Array();
1605 foreach($this->avail_tags as $tag) {
1606 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1607 array_push($_SESSION['selected_tags'], $tag);
1616 * updates sort order in session variable
1618 * this function is invoked by RPC and will sort the requested
1619 * sort order in the session variable.
1621 public function updateSortOrder($order)
1623 if(isset($this->sort_orders[$order])) {
1624 $_SESSION['sort_order'] = $order;
1628 return "unkown error";
1630 } // updateSortOrder()
1635 * this function rotates the image according the
1638 private function rotateImage($img, $degrees)
1640 if(function_exists("imagerotate")) {
1641 $img = imagerotate($img, $degrees, 0);
1643 function imagerotate($src_img, $angle)
1645 $src_x = imagesx($src_img);
1646 $src_y = imagesy($src_img);
1647 if ($angle == 180) {
1651 elseif ($src_x <= $src_y) {
1655 elseif ($src_x >= $src_y) {
1660 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1661 imagealphablending($rotate, false);
1666 for ($y = 0; $y < ($src_y); $y++) {
1667 for ($x = 0; $x < ($src_x); $x++) {
1668 $color = imagecolorat($src_img, $x, $y);
1669 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1675 for ($y = 0; $y < ($src_y); $y++) {
1676 for ($x = 0; $x < ($src_x); $x++) {
1677 $color = imagecolorat($src_img, $x, $y);
1678 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1684 for ($y = 0; $y < ($src_y); $y++) {
1685 for ($x = 0; $x < ($src_x); $x++) {
1686 $color = imagecolorat($src_img, $x, $y);
1687 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1701 $img = imagerotate($img, $degrees);
1710 * returns flipped image
1712 * this function will return an either horizontal or
1713 * vertical flipped truecolor image.
1715 private function flipImage($image, $mode)
1717 $w = imagesx($image);
1718 $h = imagesy($image);
1719 $flipped = imagecreatetruecolor($w, $h);
1723 for ($y = 0; $y < $h; $y++) {
1724 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1728 for ($x = 0; $x < $w; $x++) {
1729 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1739 * return all assigned tags for the specified photo
1741 private function get_photo_tags($idx)
1743 $result = $this->db->db_query("
1746 INNER JOIN photo_tags pt
1748 WHERE pt.photo_id='". $idx ."'
1753 while($row = $this->db->db_fetch_object($result))
1754 $tags[$row['id']] = $row['name'];
1758 } // get_photo_tags()
1761 * create on-the-fly images with text within
1763 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1765 if (strlen($color) != 6)
1768 $int = hexdec($color);
1769 $h = imagefontheight($font);
1770 $fw = imagefontwidth($font);
1771 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1772 $lines = count($txt);
1773 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1774 $bg = imagecolorallocate($im, 255, 255, 255);
1775 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1778 foreach ($txt as $text) {
1779 $x = (($w - ($fw * strlen($text))) / 2);
1780 imagestring($im, $font, $x, $y, $text, $color);
1781 $y += ($h + $space);
1784 Header("Content-type: image/png");
1787 } // showTextImage()
1790 * check if all requirements are met
1792 private function check_requirements()
1794 if(!function_exists("imagecreatefromjpeg")) {
1795 print "PHP GD library extension is missing<br />\n";
1799 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1800 print "PHP SQLite3 library extension is missing<br />\n";
1804 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1805 ini_set('track_errors', 1);
1806 @include_once 'HTML/AJAX/Server.php';
1807 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1808 print "PEAR HTML_AJAX package is missing<br />\n";
1811 @include_once 'Calendar/Calendar.php';
1812 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1813 print "PEAR Calendar package is missing<br />\n";
1816 @include_once 'Console/Getopt.php';
1817 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1818 print "PEAR Console_Getopt package is missing<br />\n";
1821 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1822 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1823 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1826 ini_restore('track_errors');
1833 } // check_requirements()
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()