3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
31 * this class contains the most functions which will to the major
39 * phpfspot configuration
47 * SQLite database handle to f-spot database
55 * SQLite database handle to phpfspot database
63 * Smarty template engine
64 * @link http://smarty.php.net smarty.php.net
65 * @see PHPFSPOT_TMPL()
79 * list of available, not-selected, tags
86 * true if runtime error occued
90 private $runtime_error = false;
93 * F-Spot database version
100 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
102 * this function will be called on class construct
103 * and will check requirements, loads configuration,
104 * open databases and start the user session
106 public function __construct()
109 * register PHPFSPOT class global
111 * @global PHPFSPOT $GLOBALS['phpfspot']
114 $GLOBALS['phpfspot'] =& $this;
116 $this->cfg = new PHPFSPOT_CFG;
118 /* verify config settings */
119 if($this->check_config_options()) {
123 /* set application name and version information */
124 $this->cfg->product = "phpfspot";
125 $this->cfg->version = "1.7";
126 $this->cfg->db_version = 2;
128 $this->sort_orders= array(
129 'date_asc' => 'Date ↑',
130 'date_desc' => 'Date ↓',
131 'name_asc' => 'Name ↑',
132 'name_desc' => 'Name ↓',
133 'tags_asc' => 'Tags ↑',
134 'tags_desc' => 'Tags ↓',
137 /* Check necessary requirements */
138 if(!$this->check_requirements()) {
142 /******* Opening F-Spot's sqlite database *********/
144 /* Check if database file exists and is readable */
145 if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n";
150 /* Check if database file is writeable */
151 if(!is_writeable($this->cfg->fspot_db)) {
152 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
153 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
154 ." speed up some database operations.\n";
158 /* open the database */
159 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
161 /* change sqlite temp directory, if requested */
162 if(isset($this->cfg->sqlite_temp_dir)) {
165 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
169 /* get F-Spot database version */
170 $this->dbver = $this->getFspotDBVersion();
172 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
173 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
177 if(!is_writeable($this->cfg->thumb_path)) {
178 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
182 /******* Opening phpfspot's sqlite database *********/
184 /* Check if directory where the database file is stored is writeable */
185 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
186 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
187 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
191 /* Check if database file is writeable */
192 if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) {
193 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
194 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
198 /* open the database */
199 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
201 /* change sqlite temp directory, if requested */
202 if(isset($this->cfg->sqlite_temp_dir)) {
203 $this->cfg_db->db_exec("
205 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
209 /* Check if some tables need to be created */
210 $this->check_phpfspot_db();
212 /* overload Smarty class with our own template handler */
213 require_once "phpfspot_tmpl.php";
214 $this->tmpl = new PHPFSPOT_TMPL();
216 /* pre-set some template variables */
217 $this->tmpl->assign('web_path', $this->cfg->web_path);
219 /* Starting with F-Spot 0.4.2, the rating-feature was available */
220 if($this->dbver > 10) {
221 $this->tmpl->assign('has_rating', true);
222 $this->sort_orders = array_merge($this->sort_orders, array(
223 'rate_asc' => 'Rate ↑',
224 'rate_desc' => 'Rate ↓',
228 /* check if all necessary indices exist */
229 $this->checkDbIndices();
231 /* if session is not yet started, do it now */
232 if(session_id() == "")
235 if(!isset($_SESSION['tag_condition']))
236 $_SESSION['tag_condition'] = 'or';
238 /* if sort-order has not been set yet, get the one specified in the config */
239 if(!isset($_SESSION['sort_order']))
240 $_SESSION['sort_order'] = $this->cfg->sort_order;
242 if(!isset($_SESSION['searchfor_tag']))
243 $_SESSION['searchfor_tag'] = '';
245 // if begin_with is still set but thumbs_per_page is now 0, unset it
246 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
247 unset($_SESSION['begin_with']);
249 // if user-friendly-url's are enabled, set also a flag for the template handler
250 if($this->is_user_friendly_url()) {
251 $this->tmpl->assign('user_friendly_url', 'true');
256 public function __destruct()
262 * show - generate html output
264 * this function can be called after the constructor has
265 * prepared everyhing. it will load the index.tpl smarty
266 * template. if necessary it will registere pre-selects
267 * (photo index, photo, tag search, date search) into
270 public function show()
272 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
273 $this->tmpl->assign('page_title', $this->cfg->page_title);
274 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
275 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
278 if($this->is_user_friendly_url()) {
279 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
282 if(isset($_GET['mode'])) {
284 $_SESSION['start_action'] = $_GET['mode'];
286 switch($_GET['mode']) {
288 if(isset($_GET['tags'])) {
289 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
291 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
292 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
294 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
295 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
299 if(isset($_GET['tags'])) {
300 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
301 $_SESSION['start_action'] = 'showp';
303 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
304 if($_SESSION['current_photo'] != $_GET['id'])
305 unset($_SESSION['current_version']);
306 $_SESSION['current_photo'] = $_GET['id'];
307 $_SESSION['start_action'] = 'showp';
309 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
310 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
312 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
313 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
317 /* fetch export template */
318 print $this->tmpl->fetch("export.tpl");
319 /* no further execution necessary. */
323 /* fetch slideshow template */
324 print $this->tmpl->show("slideshow.tpl");
325 /* no further execution necessary. */
329 if(isset($_GET['tags'])) {
330 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
332 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
333 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
335 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
336 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
344 /* if date-search variables are registered in the session, set the check
345 for "consider date-range" in the html output
347 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
348 $this->tmpl->assign('date_search_enabled', true);
350 /* if rate-search variables are registered in the session, set the check
351 for "consider rate-range" in the html output
353 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
354 $this->tmpl->assign('rate_search_enabled', true);
357 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
358 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
359 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
361 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
362 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
363 $this->tmpl->assign('rate_search', $this->get_rate_search());
365 /* if no site-content has been set yet... */
366 if(!isset($content)) {
367 /* if tags are already selected, we can immediately display photo-index */
368 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
369 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
370 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
371 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
373 /* if a photo is already selected, we can immediately display single-photo */
374 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
375 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
377 /* ok, then let us show the welcome page... */
378 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
383 $this->tmpl->assign('initial_content', $content);
385 $this->tmpl->show("index.tpl");
390 * get_tags - grab all tags of f-spot's database
392 * this function will get all available tags from
393 * the f-spot database and store them within two
394 * arrays within this class for later usage. in
395 * fact, if the user requests (hide_tags) it will
396 * opt-out some of them.
398 * this function is getting called once by show()
400 private function get_tags()
402 $this->avail_tags = Array();
405 /* if show_tags has been set in the configuration (only show photos
406 which are tagged by these tags) they following will take care,
407 that only these other tags are displayed where the photo is also
408 tagged with one of show_tags.
410 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
413 DISTINCT t1.id as id, t1.name as name
416 INNER JOIN photo_tags
417 pt2 ON pt1.photo_id=pt2.photo_id
423 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
425 t1.sort_priority ASC";
427 $result = $this->db->db_query($query_str);
431 $result = $this->db->db_query("
432 SELECT id as id,name as name
434 ORDER BY sort_priority ASC
438 while($row = $this->db->db_fetch_object($result)) {
440 $tag_id = $row['id'];
441 $tag_name = $row['name'];
443 /* if the user has specified to ignore this tag in phpfspot's
444 configuration, ignore it here so it does not get added to
447 if(in_array($row['name'], $this->cfg->hide_tags))
450 /* if you include the following if-clause and the user has specified
451 to only show certain tags which are specified in phpfspot's
452 configuration, ignore all others so they will not be added to the
454 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
455 !in_array($row['name'], $this->cfg->show_tags))
459 $this->tags[$tag_id] = $tag_name;
460 $this->avail_tags[$count] = $tag_id;
468 * get all photo details from F-Spot database
470 * this function queries the F-Spot database for all available
471 * details of the requested photo. It returns them as a object.
473 * Furthermore it takes care of the photo version to be requested.
474 * If photo version is not yet, it queries information for the
477 * @param integer $idx
478 * @return object|null
480 public function get_photo_details($idx, $version_idx = NULL)
482 /* ~ F-Spot version 0.3.x */
483 if($this->dbver < 9) {
489 p.directory_path as directory_path,
490 p.description as description
496 /* till F-Spot version 0.4.1 */
497 if($this->dbver < 11) {
503 p.description as description
509 /* rating value got introduced */
515 p.description as description,
523 /* if show_tags is set, only return details of photos which are
524 tagged with a tag that has been specified to be shown.
526 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
528 INNER JOIN photo_tags pt
532 WHERE p.id='". $idx ."'
533 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
537 WHERE p.id='". $idx ."'
541 if($row = $this->db->db_fetchSingleRow($query_str)) {
543 /* before F-Spot db version 9 there was no uri column but
544 seperated fields for directory_path and name (= filename).
546 if($this->dbver < 9) {
547 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
550 /* if version-idx has not yet been set, get the latest photo version */
551 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
552 $version_idx = $this->get_latest_version($idx);
554 /* if an alternative version has been requested. But we
555 support this only for F-Spot database versions from
558 if($version_idx > 0 && $this->dbver >= 9) {
559 /* check for alternative versions */
560 if($version = $this->db->db_fetchSingleRow("
562 version_id, name, uri
566 photo_id LIKE '". $idx ."'
568 version_id LIKE '". $version_idx ."'")) {
570 $row['name'] = $version['name'];
571 $row['uri'] = $version['uri'];
581 } // get_photo_details()
584 * returns aligned photo names
586 * this function returns aligned (length) names for a specific photo.
587 * If the length of the name exceeds $limit the name will bei
590 * @param integer $idx
591 * @param integer $limit
592 * @return string|null
594 public function getPhotoName($idx, $limit = 0)
596 if($details = $this->get_photo_details($idx)) {
597 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
598 $name = $this->shrink_text($long_name, $limit);
608 * get photo rating level
610 * this function will return the integer-based rating level of a
611 * photo. This can only be done, if the F-Spot database is at a
612 * specific version. If rating value can not be found, zero will
613 * be returned indicating no rating value is available.
618 public function get_photo_rating($idx)
620 if($detail = $this->get_photo_details($idx)) {
621 if(isset($detail['rating']))
622 return $detail['rating'];
627 } // get_photo_rating()
630 * get rate-search bars
632 * this function will return the rating-bars for the search field.
636 public function get_rate_search()
640 for($i = 1; $i <= 5; $i++) {
642 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
644 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
645 $bar.= $this->cfg->web_path ."/resources/star.png";
647 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
650 onmouseover=\"show_rate('from', ". $i .");\"
651 onmouseout=\"reset_rate('from');\"
652 onclick=\"set_rate('from', ". $i .")\" />";
657 for($i = 1; $i <= 5; $i++) {
659 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
661 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
662 $bar.= $this->cfg->web_path ."/resources/star.png";
664 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
667 onmouseover=\"show_rate('to', ". $i .");\"
668 onmouseout=\"reset_rate('to');\"
669 onclick=\"set_rate('to', ". $i .");\" />";
674 } // get_rate_search()
677 * shrink text according provided limit
679 * If the length of the name exceeds $limit, text will be shortend
680 * and inner content will be replaced with "...".
683 * @param integer $limit
686 private function shrink_text($text, $limit)
688 if($limit != 0 && strlen($text) > $limit) {
689 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
697 * translate f-spoth photo path
699 * as the full-qualified path recorded in the f-spot database
700 * is usally not the same as on the webserver, this function
701 * will replace the path with that one specified in the cfg
702 * @param string $path
705 public function translate_path($path)
707 if($this->cfg->enable_replace_path == true)
709 $this->cfg->path_replace_from,
710 $this->cfg->path_replace_to, $path);
717 * control HTML ouput for a single photo
719 * this function provides all the necessary information
720 * for the single photo template.
721 * @param integer photo
723 public function showPhoto($photo)
725 /* get all photos from the current photo selection */
726 $all_photos = $this->getPhotoSelection();
727 $count = count($all_photos);
729 for($i = 0; $i < $count; $i++) {
731 // $get_next will be set, when the photo which has to
732 // be displayed has been found - this means that the
733 // next available is in fact the NEXT image (for the
735 if(isset($get_next)) {
736 $next_img = $all_photos[$i];
740 /* the next photo is our NEXT photo */
741 if($all_photos[$i] == $photo) {
745 $previous_img = $all_photos[$i];
748 if($photo == $all_photos[$i]) {
753 $details = $this->get_photo_details($photo);
760 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
762 /* if current version is already set, use it */
763 if($this->get_current_version() !== false)
764 $version = $this->get_current_version();
766 /* if version not set yet, we assume to display the latest version */
767 if(!isset($version) || !$this->is_valid_version($photo, $version))
768 $version = $this->get_latest_version($photo);
770 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
772 if(!file_exists($orig_path)) {
773 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
777 if(!is_readable($orig_path)) {
778 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
782 /* If the thumbnail doesn't exist yet, try to create it */
783 if(!file_exists($thumb_path)) {
784 $this->gen_thumb($photo, true);
785 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
788 /* get mime-type, height and width from the original photo */
789 $info = getimagesize($orig_path);
791 /* get EXIF information if JPEG */
792 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
793 $meta = $this->get_meta_informations($orig_path);
796 /* If EXIF data are available, use them */
797 if(isset($meta['ExifImageWidth'])) {
798 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
800 $meta_res = $info[0] ."x". $info[1];
803 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
804 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
806 $extern_link = "index.php?mode=showp&id=". $photo;
807 $current_tags = $this->getCurrentTags();
808 if($current_tags != "") {
809 $extern_link.= "&tags=". $current_tags;
811 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
812 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
815 $this->tmpl->assign('extern_link', $extern_link);
817 if(!file_exists($thumb_path)) {
818 $this->_error("Can't open file ". $thumb_path ."\n");
822 $info_thumb = getimagesize($thumb_path);
824 $this->tmpl->assign('description', $details['description']);
825 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
826 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
828 $this->tmpl->assign('width', $info_thumb[0]);
829 $this->tmpl->assign('height', $info_thumb[1]);
830 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
831 $this->tmpl->assign('ExifMadeWith', $meta_make);
832 $this->tmpl->assign('ExifOrigResolution', $meta_res);
833 $this->tmpl->assign('ExifFileSize', $meta_size);
835 if($this->is_user_friendly_url()) {
836 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
837 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
840 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
841 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
844 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
846 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
847 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
848 $this->tmpl->assign('current_img', $photo);
850 if(isset($previous_img)) {
851 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
852 $this->tmpl->assign('prev_img', $previous_img);
855 if(isset($next_img)) {
856 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
857 $this->tmpl->assign('next_img', $next_img);
860 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
861 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
862 $this->tmpl->assign('photo_number', $i);
863 $this->tmpl->assign('photo_count', count($all_photos));
864 $this->tmpl->assign('photo', $photo);
865 $this->tmpl->assign('version', $version);
867 /* if the photo as alternative versions, set a flag for the template */
868 if($this->get_photo_versions($photo))
869 $this->tmpl->assign('has_versions', true);
871 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
873 return $this->tmpl->fetch("single_photo.tpl");
878 * all available tags and tag cloud
880 * this function outputs all available tags (time ordered)
881 * and in addition output them as tag cloud (tags which have
882 * many photos will appears more then others)
884 public function getAvailableTags()
886 /* retrive tags from database */
891 $result = $this->db->db_query("
892 SELECT tag_id as id, count(tag_id) as quantity
902 while($row = $this->db->db_fetch_object($result)) {
903 $tags[$row['id']] = $row['quantity'];
906 // change these font sizes if you will
907 $max_size = 125; // max font size in %
908 $min_size = 75; // min font size in %
911 $max_sat = hexdec('cc');
912 $min_sat = hexdec('44');
914 // get the largest and smallest array values
915 $max_qty = max(array_values($tags));
916 $min_qty = min(array_values($tags));
918 // find the range of values
919 $spread = $max_qty - $min_qty;
920 if (0 == $spread) { // we don't want to divide by zero
924 // determine the font-size increment
925 // this is the increase per tag quantity (times used)
926 $step = ($max_size - $min_size)/($spread);
927 $step_sat = ($max_sat - $min_sat)/($spread);
929 // loop through our tag array
930 foreach ($tags as $key => $value) {
932 /* has the currently processed tag already been added to
933 the selected tag list? if so, ignore it here...
935 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
938 // calculate CSS font-size
939 // find the $value in excess of $min_qty
940 // multiply by the font-size increment ($size)
941 // and add the $min_size set above
942 $size = $min_size + (($value - $min_qty) * $step);
943 // uncomment if you want sizes in whole %:
946 $color = $min_sat + ($value - $min_qty) * $step_sat;
952 if(isset($this->tags[$key])) {
953 if($this->is_user_friendly_url()) {
954 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
955 onclick=\"Tags('add', ". $key ."); return false;\"
957 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
958 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
961 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
962 onclick=\"Tags('add', ". $key ."); return false;\"
964 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
965 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
970 $output = substr($output, 0, strlen($output)-2);
973 } // getAvailableTags()
976 * output all selected tags
978 * this function output all tags which have been selected
979 * by the user. the selected tags are stored in the
980 * session-variable $_SESSION['selected_tags']
983 public function getSelectedTags($type = 'link')
985 /* retrive tags from database */
990 foreach($this->avail_tags as $tag)
992 // return all selected tags
993 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
998 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
1002 <div class=\"tagresulttag\">
1003 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
1004 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1014 $output = substr($output, 0, strlen($output)-2);
1018 return "no tags selected";
1021 } // getSelectedTags()
1024 * add tag to users session variable
1026 * this function will add the specified to users current
1027 * tag selection. if a date search has been made before
1028 * it will be now cleared
1031 public function addTag($tag)
1033 if(!isset($_SESSION['selected_tags']))
1034 $_SESSION['selected_tags'] = Array();
1036 if(isset($_SESSION['searchfor_tag']))
1037 unset($_SESSION['searchfor_tag']);
1039 // has the user requested to hide this tag, and still someone,
1040 // somehow tries to add it, don't allow this.
1041 if(!isset($this->cfg->hide_tags) &&
1042 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1045 if(!in_array($tag, $_SESSION['selected_tags']))
1046 array_push($_SESSION['selected_tags'], $tag);
1053 * remove tag to users session variable
1055 * this function removes the specified tag from
1056 * users current tag selection
1057 * @param string $tag
1060 public function delTag($tag)
1062 if(isset($_SESSION['searchfor_tag']))
1063 unset($_SESSION['searchfor_tag']);
1065 if(isset($_SESSION['selected_tags'])) {
1066 $key = array_search($tag, $_SESSION['selected_tags']);
1067 unset($_SESSION['selected_tags'][$key]);
1068 sort($_SESSION['selected_tags']);
1076 * reset tag selection
1078 * if there is any tag selection, it will be
1081 public function resetTags()
1083 if(isset($_SESSION['selected_tags']))
1084 unset($_SESSION['selected_tags']);
1089 * returns the value for the autocomplete tag-search
1092 public function get_xml_tag_list()
1094 if(!isset($_GET['search']) || !is_string($_GET['search']))
1095 $_GET['search'] = '';
1100 /* retrive tags from database */
1103 $matched_tags = Array();
1105 header("Content-Type: text/xml");
1107 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1108 $string.= "<results>\n";
1110 foreach($this->avail_tags as $tag)
1112 if(!empty($_GET['search']) &&
1113 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1114 count($matched_tags) < $length) {
1116 $count = $this->get_num_photos($tag);
1119 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1122 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1128 /* if we have collected enough items, break out */
1129 if(count($matched_tags) >= $length)
1133 $string.= "</results>\n";
1137 } // get_xml_tag_list()
1141 * reset single photo
1143 * if a specific photo was requested (external link)
1144 * unset the session variable now
1146 public function resetPhotoView()
1148 if(isset($_SESSION['current_photo']))
1149 unset($_SESSION['current_photo']);
1151 if(isset($_SESSION['current_version']))
1152 unset($_SESSION['current_version']);
1154 } // resetPhotoView();
1159 * if any tag search has taken place, reset it now
1161 public function resetTagSearch()
1163 if(isset($_SESSION['searchfor_tag']))
1164 unset($_SESSION['searchfor_tag']);
1166 } // resetTagSearch()
1171 * if any name search has taken place, reset it now
1173 public function resetNameSearch()
1175 if(isset($_SESSION['searchfor_name']))
1176 unset($_SESSION['searchfor_name']);
1178 } // resetNameSearch()
1183 * if any date search has taken place, reset it now.
1185 public function resetDateSearch()
1187 if(isset($_SESSION['from_date']))
1188 unset($_SESSION['from_date']);
1189 if(isset($_SESSION['to_date']))
1190 unset($_SESSION['to_date']);
1192 } // resetDateSearch();
1197 * if any rate search has taken place, reset it now.
1199 public function resetRateSearch()
1201 if(isset($_SESSION['rate_from']))
1202 unset($_SESSION['rate_from']);
1203 if(isset($_SESSION['rate_to']))
1204 unset($_SESSION['rate_to']);
1206 } // resetRateSearch();
1209 * return all photo according selection
1211 * this function returns all photos based on
1212 * the tag-selection, tag- or date-search.
1213 * the tag-search also has to take care of AND
1214 * and OR conjunctions
1217 public function getPhotoSelection()
1219 $matched_photos = Array();
1220 $additional_where_cond = "";
1222 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1223 $from_date = $_SESSION['from_date'];
1224 $to_date = $_SESSION['to_date'];
1225 $additional_where_cond.= "
1226 p.time>='". $from_date ."'
1228 p.time<='". $to_date ."'
1232 if(isset($_SESSION['searchfor_name'])) {
1234 /* check for previous conditions. if so add 'AND' */
1235 if(!empty($additional_where_cond)) {
1236 $additional_where_cond.= " AND ";
1239 if($this->dbver < 9) {
1240 $additional_where_cond.= "
1242 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1244 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1249 $additional_where_cond.= "
1251 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1253 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1259 /* limit result based on rate-search */
1260 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1262 if($this->dbver > 10) {
1264 /* check for previous conditions. if so add 'AND' */
1265 if(!empty($additional_where_cond)) {
1266 $additional_where_cond.= " AND ";
1269 $additional_where_cond.= "
1270 p.rating >= ". $_SESSION['rate_from'] ."
1272 p.rating <= ". $_SESSION['rate_to'] ."
1277 if(isset($_SESSION['sort_order'])) {
1278 $order_str = $this->get_sort_order();
1281 /* return a search result */
1282 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1285 pt1.photo_id as photo_id
1288 INNER JOIN photo_tags pt2
1289 ON pt1.photo_id=pt2.photo_id
1293 ON pt1.photo_id=p.id
1296 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1298 if(!empty($additional_where_cond))
1299 $query_str.= "AND ". $additional_where_cond ." ";
1301 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1302 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1305 if(isset($order_str))
1306 $query_str.= $order_str;
1308 $result = $this->db->db_query($query_str);
1309 while($row = $this->db->db_fetch_object($result)) {
1310 array_push($matched_photos, $row['photo_id']);
1312 return $matched_photos;
1315 /* return according the selected tags */
1316 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1318 foreach($_SESSION['selected_tags'] as $tag)
1319 $selected.= $tag .",";
1320 $selected = substr($selected, 0, strlen($selected)-1);
1322 /* photo has to match at least on of the selected tags */
1323 if($_SESSION['tag_condition'] == 'or') {
1326 pt1.photo_id as photo_id
1329 INNER JOIN photo_tags pt2
1330 ON pt1.photo_id=pt2.photo_id
1334 ON pt1.photo_id=p.id
1335 WHERE pt1.tag_id IN (". $selected .")
1337 if(!empty($additional_where_cond))
1338 $query_str.= "AND ". $additional_where_cond ." ";
1340 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1341 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1344 if(isset($order_str))
1345 $query_str.= $order_str;
1347 /* photo has to match all selected tags */
1348 elseif($_SESSION['tag_condition'] == 'and') {
1350 if(count($_SESSION['selected_tags']) >= 32) {
1351 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1352 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1356 /* Join together a table looking like
1358 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1360 so the query can quickly return all images matching the
1361 selected tags in an AND condition
1367 pt1.photo_id as photo_id
1372 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1379 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1381 INNER JOIN photo_tags pt". ($i+2) ."
1382 ON pt1.photo_id=pt". ($i+2) .".photo_id
1387 ON pt1.photo_id=p.id
1389 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1390 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1392 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1395 if(!empty($additional_where_cond))
1396 $query_str.= "AND ". $additional_where_cond;
1398 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1399 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1402 if(isset($order_str))
1403 $query_str.= $order_str;
1407 $result = $this->db->db_query($query_str);
1408 while($row = $this->db->db_fetch_object($result)) {
1409 array_push($matched_photos, $row['photo_id']);
1411 return $matched_photos;
1414 /* return all available photos */
1420 LEFT JOIN photo_tags pt
1426 if(!empty($additional_where_cond))
1427 $query_str.= "WHERE ". $additional_where_cond ." ";
1429 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1430 if(!empty($additional_where_cond))
1431 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1433 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1436 if(isset($order_str))
1437 $query_str.= $order_str;
1439 $result = $this->db->db_query($query_str);
1440 while($row = $this->db->db_fetch_object($result)) {
1441 array_push($matched_photos, $row['id']);
1443 return $matched_photos;
1445 } // getPhotoSelection()
1448 * control HTML ouput for photo index
1450 * this function provides all the necessary information
1451 * for the photo index template.
1454 public function showPhotoIndex()
1456 $photos = $this->getPhotoSelection();
1457 $current_tags = $this->getCurrentTags();
1459 $count = count($photos);
1461 /* if all thumbnails should be shown on one page */
1462 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1466 /* thumbnails should be splitted up in several pages */
1467 elseif($this->cfg->thumbs_per_page > 0) {
1469 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1473 $begin_with = $_SESSION['begin_with'];
1476 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1481 for($i = $begin_with; $i < $end_with; $i++) {
1483 if(!isset($photos[$i]))
1486 /* on first run, initalize all used variables */
1489 $images[$thumbs] = Array();
1490 $img_height[$thumbs] = Array();
1491 $img_width[$thumbs] = Array();
1492 $img_id[$thumbs] = Array();
1493 $img_name[$thumbs] = Array();
1494 $img_fullname[$thumbs] = Array();
1495 $img_title = Array();
1496 $img_rating = Array();
1499 $images[$thumbs] = $photos[$i];
1500 $img_id[$thumbs] = $i;
1501 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1502 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1503 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1504 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1506 /* get local path of the thumbnail image to be displayed */
1507 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1509 /* if the image exist and is readable, extract some details */
1510 if(file_exists($thumb_path) && is_readable($thumb_path)) {
1511 if($info = getimagesize($thumb_path) !== false) {
1512 $img_width[$thumbs] = $info[0];
1513 $img_height[$thumbs] = $info[1];
1519 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1520 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1522 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1523 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1524 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1527 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1528 $this->tmpl->assign('tag_result', 1);
1531 /* do we have to display the page selector ? */
1532 if($this->cfg->thumbs_per_page != 0) {
1536 /* calculate the page switchers */
1537 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1538 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1540 if($begin_with != 0)
1541 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1542 if($end_with < $count)
1543 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1545 $photo_per_page = $this->cfg->thumbs_per_page;
1546 $last_page = ceil($count / $photo_per_page);
1548 /* get the current selected page */
1549 if($begin_with == 0) {
1553 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1560 for($i = 1; $i <= $last_page; $i++) {
1562 if($current_page == $i)
1563 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1564 elseif($current_page-1 == $i || $current_page+1 == $i)
1565 $style = "style=\"font-size: 105%;\"";
1566 elseif(($current_page-5 >= $i) && ($i != 1) ||
1567 ($current_page+5 <= $i) && ($i != $last_page))
1568 $style = "style=\"font-size: 75%;\"";
1572 $start_with = ($i*$photo_per_page)-$photo_per_page;
1574 if($this->is_user_friendly_url()) {
1575 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1578 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1580 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1584 $select.= ">". $i ."</a> ";
1586 // until 9 pages we show the selector from 1-9
1587 if($last_page <= 9) {
1588 $page_select.= $select;
1591 if($i == 1 /* first page */ ||
1592 $i == $last_page /* last page */ ||
1593 $i == $current_page /* current page */ ||
1594 $i == ceil($last_page * 0.25) /* first quater */ ||
1595 $i == ceil($last_page * 0.5) /* half */ ||
1596 $i == ceil($last_page * 0.75) /* third quater */ ||
1597 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1598 (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 */ ||
1599 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1600 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1602 $page_select.= $select;
1610 $page_select.= "......... ";
1615 /* only show the page selector if we have more then one page */
1617 $this->tmpl->assign('page_selector', $page_select);
1620 $extern_link = "index.php?mode=showpi";
1621 $rss_link = "index.php?mode=rss";
1622 if($current_tags != "") {
1623 $extern_link.= "&tags=". $current_tags;
1624 $rss_link.= "&tags=". $current_tags;
1626 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1627 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1628 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1631 $export_link = "index.php?mode=export";
1632 $slideshow_link = "index.php?mode=slideshow";
1634 $this->tmpl->assign('extern_link', $extern_link);
1635 $this->tmpl->assign('slideshow_link', $slideshow_link);
1636 $this->tmpl->assign('export_link', $export_link);
1637 $this->tmpl->assign('rss_link', $rss_link);
1638 $this->tmpl->assign('count', $count);
1639 $this->tmpl->assign('width', $this->cfg->thumb_width);
1640 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1641 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1642 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1643 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1644 // +1 for for smarty's selection iteration
1645 $this->tmpl->assign('thumbs', $thumbs+1);
1648 $this->tmpl->assign('images', $images);
1649 $this->tmpl->assign('img_width', $img_width);
1650 $this->tmpl->assign('img_height', $img_height);
1651 $this->tmpl->assign('img_id', $img_id);
1652 $this->tmpl->assign('img_name', $img_name);
1653 $this->tmpl->assign('img_fullname', $img_fullname);
1654 $this->tmpl->assign('img_title', $img_title);
1655 $this->tmpl->assign('img_rating', $img_rating);
1658 $result = $this->tmpl->fetch("photo_index.tpl");
1660 /* if we are returning to photo index from an photo-view,
1661 scroll the window to the last shown photo-thumbnail.
1662 after this, unset the last_photo session variable.
1664 if(isset($_SESSION['last_photo'])) {
1665 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1666 unset($_SESSION['last_photo']);
1671 } // showPhotoIndex()
1674 * show credit template
1676 public function showCredits()
1678 $this->tmpl->assign('version', $this->cfg->version);
1679 $this->tmpl->assign('product', $this->cfg->product);
1680 $this->tmpl->assign('db_version', $this->dbver);
1681 $this->tmpl->show("credits.tpl");
1686 * create thumbnails for the requested width
1688 * this function creates image thumbnails of $orig_image
1689 * stored as $thumb_image. It will check if the image is
1690 * in a supported format, if necessary rotate the image
1691 * (based on EXIF orientation meta headers) and re-sizing.
1692 * @param string $orig_image
1693 * @param string $thumb_image
1694 * @param integer $width
1697 public function create_thumbnail($orig_image, $thumb_image, $width)
1699 if(!file_exists($orig_image)) {
1703 $mime = $this->get_mime_info($orig_image);
1705 /* check if original photo is a support image type */
1706 if(!$this->checkifImageSupported($mime))
1713 $meta = $this->get_meta_informations($orig_image);
1719 if(isset($meta['Orientation'])) {
1720 switch($meta['Orientation']) {
1721 case 1: /* top, left */
1722 /* nothing to do */ break;
1723 case 2: /* top, right */
1724 $rotate = 0; $flip_hori = true; break;
1725 case 3: /* bottom, left */
1726 $rotate = 180; break;
1727 case 4: /* bottom, right */
1728 $flip_vert = true; break;
1729 case 5: /* left side, top */
1730 $rotate = 90; $flip_vert = true; break;
1731 case 6: /* right side, top */
1732 $rotate = 90; break;
1733 case 7: /* left side, bottom */
1734 $rotate = 270; $flip_vert = true; break;
1735 case 8: /* right side, bottom */
1736 $rotate = 270; break;
1740 $src_img = @imagecreatefromjpeg($orig_image);
1746 $src_img = @imagecreatefrompng($orig_image);
1750 case 'image/x-portable-pixmap':
1752 $src_img = new Imagick($orig_image);
1753 $handler = "imagick";
1758 if(!isset($src_img) || empty($src_img)) {
1759 print "Can't load image from ". $orig_image ."\n";
1767 /* grabs the height and width */
1768 $cur_width = imagesx($src_img);
1769 $cur_height = imagesy($src_img);
1771 // If requested width is more then the actual image width,
1772 // do not generate a thumbnail, instead safe the original
1773 // as thumbnail but with lower quality. But if the image
1774 // is to heigh too, then we still have to resize it.
1775 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1776 $result = imagejpeg($src_img, $thumb_image, 75);
1777 imagedestroy($src_img);
1784 $cur_width = $src_img->getImageWidth();
1785 $cur_height = $src_img->getImageHeight();
1787 // If requested width is more then the actual image width,
1788 // do not generate a thumbnail, instead safe the original
1789 // as thumbnail but with lower quality. But if the image
1790 // is to heigh too, then we still have to resize it.
1791 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1792 $src_img->setCompressionQuality(75);
1793 $src_img->setImageFormat('jpeg');
1794 $src_img->writeImage($thumb_image);
1796 $src_img->destroy();
1803 // If the image will be rotate because EXIF orientation said so
1804 // 'virtually rotate' the image for further calculations
1805 if($rotate == 90 || $rotate == 270) {
1807 $cur_width = $cur_height;
1811 /* calculates aspect ratio */
1812 $aspect_ratio = $cur_height / $cur_width;
1815 if($aspect_ratio < 1) {
1817 $new_h = abs($new_w * $aspect_ratio);
1819 /* 'virtually' rotate the image and calculate it's ratio */
1820 $tmp_w = $cur_height;
1821 $tmp_h = $cur_width;
1822 /* now get the ratio from the 'rotated' image */
1823 $tmp_ratio = $tmp_h/$tmp_w;
1824 /* now calculate the new dimensions */
1826 $tmp_h = abs($tmp_w * $tmp_ratio);
1828 // now that we know, how high they photo should be, if it
1829 // gets rotated, use this high to scale the image
1831 $new_w = abs($new_h / $aspect_ratio);
1833 // If the image will be rotate because EXIF orientation said so
1834 // now 'virtually rotate' back the image for the image manipulation
1835 if($rotate == 90 || $rotate == 270) {
1846 /* creates new image of that size */
1847 $dst_img = imagecreatetruecolor($new_w, $new_h);
1849 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1851 /* copies resized portion of original image into new image */
1852 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1854 /* needs the image to be flipped horizontal? */
1856 $this->_debug("(FLIP)");
1857 $dst_img = $this->flipImage($dst_img, 'hori');
1859 /* needs the image to be flipped vertical? */
1861 $this->_debug("(FLIP)");
1862 $dst_img = $this->flipImage($dst_img, 'vert');
1866 $this->_debug("(ROTATE)");
1867 $dst_img = $this->rotateImage($dst_img, $rotate);
1870 /* write down new generated file */
1871 $result = imagejpeg($dst_img, $thumb_image, 75);
1873 /* free your mind */
1874 imagedestroy($dst_img);
1875 imagedestroy($src_img);
1877 if($result === false) {
1878 print "Can't write thumbnail ". $thumb_image ."\n";
1888 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1890 /* needs the image to be flipped horizontal? */
1892 $this->_debug("(FLIP)");
1893 $src_img->rotateImage(new ImagickPixel(), 90);
1894 $src_img->flipImage();
1895 $src_img->rotateImage(new ImagickPixel(), -90);
1897 /* needs the image to be flipped vertical? */
1899 $this->_debug("(FLIP)");
1900 $src_img->flipImage();
1904 $this->_debug("(ROTATE)");
1905 $src_img->rotateImage(new ImagickPixel(), $rotate);
1908 $src_img->setCompressionQuality(75);
1909 $src_img->setImageFormat('jpeg');
1911 if(!$src_img->writeImage($thumb_image)) {
1912 print "Can't write thumbnail ". $thumb_image ."\n";
1917 $src_img->destroy();
1924 } // create_thumbnail()
1927 * return all exif meta data from the file
1928 * @param string $file
1931 public function get_meta_informations($file)
1933 return exif_read_data($file);
1935 } // get_meta_informations()
1938 * create phpfspot own sqlite database
1940 * this function creates phpfspots own sqlite database
1941 * if it does not exist yet. this own is used to store
1942 * some necessary informations (md5 sum's, ...).
1944 public function check_phpfspot_db()
1946 // if the config table doesn't exist yet, create it
1947 if(!$this->cfg_db->db_check_table_exists("images")) {
1948 $this->cfg_db->db_exec("
1949 CREATE TABLE images (
1951 img_version_idx int,
1952 img_md5 varchar(32),
1953 UNIQUE(img_idx, img_version_idx)
1958 if(!$this->cfg_db->db_check_table_exists("meta")) {
1959 $this->cfg_db->db_exec("
1961 meta_key varchar(255),
1962 meta_value varchar(255)
1966 /* db_version was added with phpfspot 1.7, before changes
1967 on the phpfspot database where not necessary.
1970 $this->cfg_db->db_exec("
1972 meta_key, meta_value
1974 'phpfspot Database Version',
1975 '". $this->cfg->db_version ."'
1980 /* if version <= 2 and column img_version_idx does not exist yet */
1981 if($this->get_db_version() <= 2 &&
1982 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
1984 if(!$this->cfg_db->db_start_transaction())
1985 die("Can not start database transaction");
1987 $result = $this->cfg_db->db_exec("
1988 CREATE TEMPORARY TABLE images_temp (
1990 img_version_idx int,
1991 img_md5 varchar(32),
1992 UNIQUE(img_idx, img_version_idx)
1997 $this->cfg_db->db_rollback_transaction();
1998 die("Upgrade failed - transaction rollback");
2001 $result = $this->cfg_db->db_exec("
2002 INSERT INTO images_temp
2011 $this->cfg_db->db_rollback_transaction();
2012 die("Upgrade failed - transaction rollback");
2015 $result = $this->cfg_db->db_exec("
2020 $this->cfg_db->db_rollback_transaction();
2021 die("Upgrade failed - transaction rollback");
2024 $result = $this->cfg_db->db_exec("
2025 CREATE TABLE images (
2027 img_version_idx int,
2028 img_md5 varchar(32),
2029 UNIQUE(img_idx, img_version_idx)
2034 $this->cfg_db->db_rollback_transaction();
2035 die("Upgrade failed - transaction rollback");
2038 $result = $this->cfg_db->db_exec("
2045 $this->cfg_db->db_rollback_transaction();
2046 die("Upgrade failed - transaction rollback");
2049 $result = $this->cfg_db->db_exec("
2050 DROP TABLE images_temp
2054 $this->cfg_db->db_rollback_transaction();
2055 die("Upgrade failed - transaction rollback");
2058 if(!$this->cfg_db->db_commit_transaction())
2059 die("Can not commit database transaction");
2063 } // check_phpfspot_db
2066 * generates thumbnails
2068 * This function generates JPEG thumbnails from
2069 * provided F-Spot photo indize and its alternative
2072 * 1. Check if all thumbnail generations (width) are already in place and
2074 * 2. Check if the md5sum of the original file has changed
2075 * 3. Generate the thumbnails if needed
2076 * @param integer $idx
2077 * @param integer $force
2078 * @param boolean $overwrite
2080 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2083 $versions = Array(0);
2085 $resolutions = Array(
2086 $this->cfg->thumb_width,
2087 $this->cfg->photo_width,
2088 $this->cfg->mini_width,
2092 if($alt_versions = $this->get_photo_versions($idx))
2093 $versions = array_merge($versions, $alt_versions);
2095 foreach($versions as $version) {
2097 /* get details from F-Spot's database */
2098 $details = $this->get_photo_details($idx, $version);
2100 /* calculate file MD5 sum */
2101 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2103 if(!file_exists($full_path)) {
2104 $this->_error("File ". $full_path ." does not exist");
2108 if(!is_readable($full_path)) {
2109 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2113 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2115 /* If Nikon NEF format, we need to treat it another way */
2116 if(isset($this->cfg->dcraw_bin) &&
2117 file_exists($this->cfg->dcraw_bin) &&
2118 is_executable($this->cfg->dcraw_bin) &&
2119 preg_match('/\.nef$/i', $details['uri'])) {
2121 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2123 /* if PPM file does not exist, let dcraw convert it from NEF */
2124 if(!file_exists($ppm_path)) {
2125 system($this->cfg->dcraw_bin ." -a ". $full_path);
2128 /* for now we handle the PPM instead of the NEF */
2129 $full_path = $ppm_path;
2133 $file_md5 = md5_file($full_path);
2136 foreach($resolutions as $resolution) {
2138 $generate_it = false;
2140 $thumb_sub_path = substr($file_md5, 0, 2);
2141 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2143 /* if thumbnail-subdirectory does not exist yet, create it */
2144 if(!file_exists(dirname($thumb_path))) {
2145 mkdir(dirname($thumb_path), 0755);
2148 /* if the thumbnail file doesn't exist, create it */
2149 if(!file_exists($thumb_path) || $force) {
2150 $generate_it = true;
2152 elseif($file_md5 != $this->getMD5($idx, $version)) {
2153 $generate_it = true;
2156 if($generate_it || $overwrite) {
2158 $this->_debug(" ". $resolution ."px");
2159 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2167 $this->_debug(" already exist");
2170 /* set the new/changed MD5 sum for the current photo */
2172 $this->setMD5($idx, $file_md5, $version);
2175 $this->_debug("\n");
2182 * returns stored md5 sum for a specific photo
2184 * this function queries the phpfspot database for a stored MD5
2185 * checksum of the specified photo. It also takes care of the
2186 * requested photo version - original or alternative photo.
2188 * @param integer $idx
2189 * @return string|null
2191 public function getMD5($idx, $version_idx = 0)
2193 $result = $this->cfg_db->db_query("
2199 img_idx='". $idx ."'
2201 img_version_idx='". $version_idx ."'
2207 if($img = $this->cfg_db->db_fetch_object($result))
2208 return $img['img_md5'];
2215 * set MD5 sum for the specific photo
2216 * @param integer $idx
2217 * @param string $md5
2219 private function setMD5($idx, $md5, $version_idx = 0)
2221 $result = $this->cfg_db->db_exec("
2222 INSERT OR REPLACE INTO images (
2223 img_idx, img_version_idx, img_md5
2226 '". $version_idx ."',
2234 * store current tag condition
2236 * this function stores the current tag condition
2237 * (AND or OR) in the users session variables
2238 * @param string $mode
2241 public function setTagCondition($mode)
2243 $_SESSION['tag_condition'] = $mode;
2247 } // setTagCondition()
2250 * invoke tag & date search
2252 * this function will return all matching tags and store
2253 * them in the session variable selected_tags. furthermore
2254 * it also handles the date search.
2255 * getPhotoSelection() will then only return the matching
2259 public function startSearch()
2262 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2263 $date_from = $_POST['date_from'];
2265 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2266 $date_to = $_POST['date_to'];
2269 /* tag-name search */
2270 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2271 $searchfor_tag = $_POST['for_tag'];
2272 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2275 unset($_SESSION['searchfor_tag']);
2278 /* file-name search */
2279 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2280 $_SESSION['searchfor_name'] = $_POST['for_name'];
2283 unset($_SESSION['searchfor_name']);
2287 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2289 $_SESSION['rate_from'] = $_POST['rate_from'];
2291 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2292 $_SESSION['rate_to'] = $_POST['rate_to'];
2296 /* delete any previously set value */
2297 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2302 if(isset($date_from) && !empty($date_from))
2303 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2305 unset($_SESSION['from_date']);
2307 if(isset($date_to) && !empty($date_to))
2308 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2310 unset($_SESSION['to_date']);
2312 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2313 /* new search, reset the current selected tags */
2314 $_SESSION['selected_tags'] = Array();
2315 foreach($this->avail_tags as $tag) {
2316 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2317 array_push($_SESSION['selected_tags'], $tag);
2326 * updates sort order in session variable
2328 * this function is invoked by RPC and will sort the requested
2329 * sort order in the session variable.
2330 * @param string $sort_order
2333 public function updateSortOrder($order)
2335 if(isset($this->sort_orders[$order])) {
2336 $_SESSION['sort_order'] = $order;
2340 return "unkown error";
2342 } // updateSortOrder()
2345 * update photo version in session variable
2347 * this function is invoked by RPC and will set the requested
2348 * photo version in the session variable.
2349 * @param string $photo_version
2352 public function update_photo_version($photo_idx, $photo_version)
2354 if($this->is_valid_version($photo_idx, $photo_version)) {
2355 $_SESSION['current_version'] = $photo_version;
2359 return "incorrect photo version provided";
2361 } // update_photo_version()
2366 * this function rotates the image according the
2368 * @param string $img
2369 * @param integer $degress
2372 private function rotateImage($img, $degrees)
2374 if(function_exists("imagerotate")) {
2375 $img = imagerotate($img, $degrees, 0);
2377 function imagerotate($src_img, $angle)
2379 $src_x = imagesx($src_img);
2380 $src_y = imagesy($src_img);
2381 if ($angle == 180) {
2385 elseif ($src_x <= $src_y) {
2389 elseif ($src_x >= $src_y) {
2394 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2395 imagealphablending($rotate, false);
2400 for ($y = 0; $y < ($src_y); $y++) {
2401 for ($x = 0; $x < ($src_x); $x++) {
2402 $color = imagecolorat($src_img, $x, $y);
2403 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2409 for ($y = 0; $y < ($src_y); $y++) {
2410 for ($x = 0; $x < ($src_x); $x++) {
2411 $color = imagecolorat($src_img, $x, $y);
2412 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2418 for ($y = 0; $y < ($src_y); $y++) {
2419 for ($x = 0; $x < ($src_x); $x++) {
2420 $color = imagecolorat($src_img, $x, $y);
2421 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2435 $img = imagerotate($img, $degrees);
2444 * returns flipped image
2446 * this function will return an either horizontal or
2447 * vertical flipped truecolor image.
2448 * @param string $image
2449 * @param string $mode
2452 private function flipImage($image, $mode)
2454 $w = imagesx($image);
2455 $h = imagesy($image);
2456 $flipped = imagecreatetruecolor($w, $h);
2460 for ($y = 0; $y < $h; $y++) {
2461 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2465 for ($x = 0; $x < $w; $x++) {
2466 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2476 * return all assigned tags for the specified photo
2477 * @param integer $idx
2480 private function get_photo_tags($idx)
2482 $result = $this->db->db_query("
2483 SELECT t.id as id, t.name as name
2485 INNER JOIN photo_tags pt
2487 WHERE pt.photo_id='". $idx ."'
2492 while($row = $this->db->db_fetch_object($result)) {
2493 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2495 $tags[$row['id']] = $row['name'];
2500 } // get_photo_tags()
2503 * create on-the-fly images with text within
2504 * @param string $txt
2505 * @param string $color
2506 * @param integer $space
2507 * @param integer $font
2510 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2512 if (strlen($color) != 6)
2515 $int = hexdec($color);
2516 $h = imagefontheight($font);
2517 $fw = imagefontwidth($font);
2518 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2519 $lines = count($txt);
2520 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2521 $bg = imagecolorallocate($im, 255, 255, 255);
2522 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2525 foreach ($txt as $text) {
2526 $x = (($w - ($fw * strlen($text))) / 2);
2527 imagestring($im, $font, $x, $y, $text, $color);
2528 $y += ($h + $space);
2531 Header("Content-type: image/png");
2534 } // showTextImage()
2537 * check if all requirements are met
2540 private function check_requirements()
2542 if(!function_exists("imagecreatefromjpeg")) {
2543 print "PHP GD library extension is missing<br />\n";
2547 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2548 print "PHP SQLite3 library extension is missing<br />\n";
2552 if($this->cfg->db_access == "pdo") {
2553 if(array_search("sqlite", PDO::getAvailableDrivers()) === false) {
2554 print "PDO SQLite3 driver is missing<br />\n";
2559 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2560 ini_set('track_errors', 1);
2561 @include_once 'HTML/AJAX/Server.php';
2562 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2563 print "PEAR HTML_AJAX package is missing<br />\n";
2566 @include_once 'Calendar/Calendar.php';
2567 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2568 print "PEAR Calendar package is missing<br />\n";
2571 @include_once 'Console/Getopt.php';
2572 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2573 print "PEAR Console_Getopt package is missing<br />\n";
2576 @include_once 'Date.php';
2577 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2578 print "PEAR Date package is missing<br />\n";
2581 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2582 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2583 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2586 ini_restore('track_errors');
2593 } // check_requirements()
2595 private function _debug($text)
2597 if(isset($this->fromcmd)) {
2604 * check if specified MIME type is supported
2605 * @param string $mime
2608 public function checkifImageSupported($mime)
2610 $supported_types = Array(
2613 "image/x-portable-pixmap",
2617 if(in_array($mime, $supported_types))
2622 } // checkifImageSupported()
2626 * @param string $text
2628 public function _error($text)
2630 switch($this->cfg->logging) {
2633 if(isset($this->fromcmd))
2636 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2637 print $text ."<br />\n";
2644 error_log($text, 3, $this->cfg->log_file);
2648 $this->runtime_error = true;
2653 * get calendar input-text fields
2655 * this function returns a text-field used for the data selection.
2656 * Either it will be filled with the current date or, if available,
2657 * filled with the date user entered previously.
2659 * @param string $mode
2662 private function get_date_text_field($mode)
2664 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2666 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2668 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2670 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2671 if(!isset($_SESSION[$mode .'_date']))
2672 $output.= " disabled=\"disabled\"";
2677 } // get_date_text_field()
2680 * output calendar matrix
2681 * @param integer $year
2682 * @param integer $month
2683 * @param integer $day
2685 public function get_calendar_matrix($userdate)
2687 if(($userdate = strtotime($userdate)) === false) {
2694 $date->setDate($userdate);
2696 $year = $date->getYear();
2697 $month = $date->getMonth();
2698 $day = $date->getDay();
2705 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2706 require_once CALENDAR_ROOT.'Day.php';
2709 $month_cal = new Calendar_Month_Weekdays($year,$month);
2712 $prevStamp = $month_cal->prevMonth(true);
2713 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2714 $nextStamp = $month_cal->nextMonth(true);
2715 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2717 $selectedDays = array (
2718 new Calendar_Day($year,$month,$day),
2721 // Build the days in the month
2722 $month_cal->build($selectedDays);
2724 $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp()));
2725 $this->tmpl->assign('prev_month', $prev);
2726 $this->tmpl->assign('next_month', $next);
2728 while ( $day = $month_cal->fetch() ) {
2730 if(!isset($matrix[$rows]))
2731 $matrix[$rows] = Array();
2735 $dayStamp = $day->thisDay(true);
2736 $link = "javascript:setCalendarDate('"
2737 . date('Y',$dayStamp)
2739 . date('m',$dayStamp)
2741 . date('d',$dayStamp)
2744 // isFirst() to find start of week
2745 if ( $day->isFirst() )
2748 if ( $day->isSelected() ) {
2749 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2750 } else if ( $day->isEmpty() ) {
2751 $string.= "<td> </td>\n";
2753 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2756 // isLast() to find end of week
2757 if ( $day->isLast() )
2758 $string.= "</tr>\n";
2760 $matrix[$rows][$cols] = $string;
2770 $this->tmpl->assign('matrix', $matrix);
2771 $this->tmpl->assign('rows', $rows);
2772 $this->tmpl->show("calendar.tpl");
2774 } // get_calendar_matrix()
2777 * output export page
2778 * @param string $mode
2780 public function getExport($mode)
2782 $pictures = $this->getPhotoSelection();
2783 $current_tags = $this->getCurrentTags();
2785 foreach($pictures as $picture) {
2787 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2788 if($current_tags != "") {
2789 $orig_url.= "&tags=". $current_tags;
2791 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2792 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2795 if($this->is_user_friendly_url()) {
2796 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2799 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2805 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2806 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2810 // "[%pictureurl% %thumbnailurl%]"
2811 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2814 case 'MoinMoinList':
2815 // " * [%pictureurl% %thumbnailurl%]"
2816 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2827 public function getRSSFeed()
2829 Header("Content-type: text/xml; charset=utf-8");
2830 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2833 xmlns:media="http://search.yahoo.com/mrss/"
2834 xmlns:dc="http://purl.org/dc/elements/1.1/"
2837 <title>phpfspot</title>
2838 <description>phpfspot RSS feed</description>
2839 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2840 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2841 <generator>phpfspot</generator>
2844 $pictures = $this->getPhotoSelection();
2845 $current_tags = $this->getCurrentTags();
2847 foreach($pictures as $picture) {
2849 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2850 if($current_tags != "") {
2851 $orig_url.= "&tags=". $current_tags;
2853 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2854 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2857 $details = $this->get_photo_details($picture);
2859 if($this->is_user_friendly_url()) {
2860 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2863 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2866 $thumb_html = htmlspecialchars("
2867 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2869 ". $details['description']);
2871 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2873 /* get EXIF information if JPEG */
2874 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2875 $meta = $this->get_meta_informations($orig_path);
2880 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2881 <link><?php print htmlspecialchars($orig_url); ?></link>
2882 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2883 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2885 <?php print $thumb_html; ?>
2887 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2902 * get all selected tags
2904 * This function will return all selected tags as one string, seperated
2908 private function getCurrentTags()
2911 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2912 foreach($_SESSION['selected_tags'] as $tag)
2913 $current_tags.= $tag .",";
2914 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2916 return $current_tags;
2918 } // getCurrentTags()
2921 * return the current photo
2923 public function get_current_photo()
2925 if(isset($_SESSION['current_photo'])) {
2926 return $_SESSION['current_photo'];
2931 } // get_current_photo()
2934 * current selected photo version
2936 * this function returns the current selected photo version
2937 * from the session variables.
2941 public function get_current_version()
2943 /* if current version is set, return it, if the photo really has that version */
2944 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2945 return $_SESSION['current_version'];
2949 } // get_current_version()
2952 * returns latest available photo version
2954 * this function returns the latested available version
2955 * for the requested photo.
2959 public function get_latest_version($photo_idx)
2961 /* try to get the lasted version for the current photo */
2962 if($versions = $this->get_photo_versions($photo_idx))
2963 return $versions[count($versions)-1];
2965 /* if no alternative version were found, return original version */
2968 } // get_current_version()
2971 * tells the client browser what to do
2973 * this function is getting called via AJAX by the
2974 * client browsers. it will tell them what they have
2975 * to do next. This is necessary for directly jumping
2976 * into photo index or single photo view when the are
2977 * requested with specific URLs
2980 public function whatToDo()
2982 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2984 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2985 return "showpi_tags";
2987 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2994 * return the current process-user
2997 private function getuid()
2999 if($uid = posix_getuid()) {
3000 if($user = posix_getpwuid($uid)) {
3001 return $user['name'];
3010 * photo version select list
3012 * this function returns a HTML select list (drop down)
3013 * to select a alternative photo version of the original photo.
3015 * @param array $params
3016 * @param smarty $smarty
3019 public function smarty_photo_version_select_list($params, &$smarty)
3021 if(!isset($params['photo']) || !isset($params['current']))
3024 $output = "<option value=\"0\">Original</option>";
3025 $versions = $this->get_photo_versions($params['photo']);
3027 foreach($versions as $version) {
3029 $output.= "<option value=\"". $version ."\"";
3030 if($version == $params['current']) {
3031 $output.= " selected=\"selected\"";
3033 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3038 } // smarty_photo_version_select_list()
3041 * returns a select-dropdown box to select photo index sort parameters
3042 * @param array $params
3043 * @param smarty $smarty
3046 public function smarty_sort_select_list($params, &$smarty)
3050 foreach($this->sort_orders as $key => $value) {
3051 $output.= "<option value=\"". $key ."\"";
3052 if($key == $_SESSION['sort_order']) {
3053 $output.= " selected=\"selected\"";
3055 $output.= ">". $value ."</option>";
3060 } // smarty_sort_select_list()
3063 * returns the currently selected sort order
3066 private function get_sort_order()
3068 switch($_SESSION['sort_order']) {
3070 return " ORDER BY p.time ASC";
3073 return " ORDER BY p.time DESC";
3076 if($this->dbver < 9) {
3077 return " ORDER BY p.name ASC";
3080 return " ORDER BY basename(p.uri) ASC";
3084 if($this->dbver < 9) {
3085 return " ORDER BY p.name DESC";
3088 return " ORDER BY basename(p.uri) DESC";
3092 return " ORDER BY t.name ASC ,p.time ASC";
3095 return " ORDER BY t.name DESC ,p.time ASC";
3098 return " ORDER BY p.rating ASC, t.name ASC";
3101 return " ORDER BY p.rating DESC, t.name ASC";
3105 } // get_sort_order()
3108 * return the next to be shown slide show image
3110 * this function returns the URL of the next image
3111 * in the slideshow sequence.
3114 public function getNextSlideShowImage()
3116 $all_photos = $this->getPhotoSelection();
3118 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3119 $_SESSION['slideshow_img'] = 0;
3121 $_SESSION['slideshow_img']++;
3123 if($this->is_user_friendly_url()) {
3124 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3127 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3129 } // getNextSlideShowImage()
3132 * return the previous to be shown slide show image
3134 * this function returns the URL of the previous image
3135 * in the slideshow sequence.
3138 public function getPrevSlideShowImage()
3140 $all_photos = $this->getPhotoSelection();
3142 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3143 $_SESSION['slideshow_img'] = 0;
3145 $_SESSION['slideshow_img']--;
3147 if($this->is_user_friendly_url()) {
3148 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3151 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3153 } // getPrevSlideShowImage()
3155 public function resetSlideShow()
3157 if(isset($_SESSION['slideshow_img']))
3158 unset($_SESSION['slideshow_img']);
3160 } // resetSlideShow()
3165 * this function will get all photos from the fspot
3166 * database and randomly return ONE entry
3168 * saddly there is yet no sqlite3 function which returns
3169 * the bulk result in array, so we have to fill up our
3173 public function get_random_photo()
3182 /* if show_tags is set, only return details for photos which
3183 are specified to be shown
3185 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3187 INNER JOIN photo_tags pt
3192 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3195 $result = $this->db->db_query($query_str);
3197 while($row = $this->db->db_fetch_object($result)) {
3198 array_push($all, $row['id']);
3201 return $all[array_rand($all)];
3203 } // get_random_photo()
3206 * get random photo tag photo
3208 * this function will get all photos tagged with the requested
3209 * tag from the fspot database and randomly return ONE entry
3211 * saddly there is yet no sqlite3 function which returns
3212 * the bulk result in array, so we have to fill up our
3216 public function get_random_tag_photo($tagidx)
3220 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3223 DISTINCT pt1.photo_id as id
3226 INNER JOIN photo_tags
3227 pt2 ON pt1.photo_id=pt2.photo_id
3233 pt1.tag_id LIKE '". $tagidx ."'
3235 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3237 t1.sort_priority ASC";
3245 INNER JOIN photo_tags pt
3248 pt.tag_id LIKE '". $tagidx ."'";
3251 $result = $this->db->db_query($query_str);
3253 while($row = $this->db->db_fetch_object($result)) {
3254 array_push($all, $row['id']);
3257 return $all[array_rand($all)];
3259 } // get_random_tag_photo()
3262 * validates provided date
3264 * this function validates if the provided date
3265 * contains a valid date and will return true
3267 * @param string $date_str
3270 public function isValidDate($date_str)
3272 $timestamp = strtotime($date_str);
3274 if(is_numeric($timestamp))
3282 * timestamp to string conversion
3283 * @param integer $timestamp
3286 private function ts2str($timestamp)
3288 if(!empty($timestamp) && is_numeric($timestamp))
3289 return strftime("%Y-%m-%d", $timestamp);
3294 * extract tag-names from $_GET['tags']
3295 * @param string $tags_str
3298 private function extractTags($tags_str)
3300 $not_validated = split(',', $tags_str);
3301 $validated = array();
3303 foreach($not_validated as $tag) {
3304 if(is_numeric($tag))
3305 array_push($validated, $tag);
3313 * returns the full path to a thumbnail
3314 * @param integer $width
3315 * @param integer $photo
3318 public function get_thumb_path($width, $photo_idx, $version_idx)
3320 $md5 = $this->getMD5($photo_idx, $version_idx);
3321 $sub_path = substr($md5, 0, 2);
3322 return $this->cfg->thumb_path
3330 } // get_thumb_path()
3333 * returns server's virtual host name
3336 private function get_server_name()
3338 return $_SERVER['SERVER_NAME'];
3339 } // get_server_name()
3342 * returns type of webprotocol which is currently used
3345 private function get_web_protocol()
3347 if(!isset($_SERVER['HTTPS']))
3351 } // get_web_protocol()
3354 * return url to this phpfspot installation
3357 private function get_phpfspot_url()
3359 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3361 } // get_phpfspot_url()
3364 * returns the number of photos which are tagged with $tag_id
3365 * @param integer $tag_id
3368 public function get_num_photos($tag_id)
3370 if($result = $this->db->db_fetchSingleRow("
3371 SELECT count(*) as number
3374 tag_id LIKE '". $tag_id ."'")) {
3376 return $result['number'];
3382 } // get_num_photos()
3385 * check file exists and is readable
3387 * returns true, if everything is ok, otherwise false
3388 * if $silent is not set, this function will output and
3390 * @param string $file
3391 * @param boolean $silent
3394 private function check_readable($file, $silent = null)
3396 if(!file_exists($file)) {
3398 print "File \"". $file ."\" does not exist.\n";
3402 if(!is_readable($file)) {
3404 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3410 } // check_readable()
3413 * check if all needed indices are present
3415 * this function checks, if some needed indices are already
3416 * present, or if not, create them on the fly. they are
3417 * necessary to speed up some queries like that one look for
3418 * all tags, when show_tags is specified in the configuration.
3420 private function checkDbIndices()
3422 $result = $this->db->db_exec("
3423 CREATE INDEX IF NOT EXISTS
3430 } // checkDbIndices()
3433 * retrive F-Spot database version
3435 * this function will return the F-Spot database version number
3436 * It is stored within the sqlite3 database in the table meta
3437 * @return string|null
3439 public function getFspotDBVersion()
3441 if($result = $this->db->db_fetchSingleRow("
3442 SELECT data as version
3445 name LIKE 'F-Spot Database Version'
3447 return $result['version'];
3451 } // getFspotDBVersion()
3454 * parse the provided URI and will returned the requested chunk
3455 * @param string $uri
3456 * @param string $mode
3459 public function parse_uri($uri, $mode)
3461 if(($components = parse_url($uri)) !== false) {
3465 return basename($components['path']);
3468 return dirname($components['path']);
3471 return $components['path'];
3481 * validate config options
3483 * this function checks if all necessary configuration options are
3484 * specified and set.
3487 private function check_config_options()
3489 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3490 $this->_error("Please set \$page_title in phpfspot_cfg");
3492 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3493 $this->_error("Please set \$base_path in phpfspot_cfg");
3495 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3496 $this->_error("Please set \$web_path in phpfspot_cfg");
3498 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3499 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3501 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3502 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3504 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3505 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3507 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3508 $this->_error("Please set \$db_access in phpfspot_cfg");
3510 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3511 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3513 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3514 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3516 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3517 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3519 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3520 $this->_error("Please set \$photo_width in phpfspot_cfg");
3522 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3523 $this->_error("Please set \$mini_width in phpfspot_cfg");
3525 if(!isset($this->cfg->thumbs_per_page))
3526 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3528 if(!isset($this->cfg->enable_replace_path))
3529 $this->_error("Please set \$enable_replace_path in phpfspot_cfg");
3531 if($this->cfg->enable_replace_path == true) {
3532 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3533 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3535 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3536 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3539 if(!isset($this->cfg->hide_tags))
3540 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3542 if(!isset($this->cfg->theme_name))
3543 $this->_error("Please set \$theme_name in phpfspot_cfg");
3545 if(!isset($this->cfg->logging))
3546 $this->_error("Please set \$logging in phpfspot_cfg");
3548 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3550 if(!isset($this->cfg->log_file))
3551 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3553 if(!is_writeable($this->cfg->log_file))
3554 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3558 /* remove trailing slash, if set */
3559 if($this->cfg->web_path == "/")
3560 $this->cfg->web_path = "";
3561 elseif(preg_match('/\/$/', $this->cfg->web_path))
3562 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3564 return $this->runtime_error;
3566 } // check_config_options()
3569 * cleanup phpfspot own database
3571 * When photos are getting delete from F-Spot, there will remain
3572 * remain some residues in phpfspot own database. This function
3573 * will try to wipe them out.
3575 public function cleanup_phpfspot_db()
3577 $to_delete = Array();
3579 $result = $this->cfg_db->db_query("
3580 SELECT img_idx as img_idx
3582 ORDER BY img_idx ASC
3585 while($row = $this->cfg_db->db_fetch_object($result)) {
3586 if(!$this->db->db_fetchSingleRow("
3589 WHERE id='". $row['img_idx'] ."'")) {
3591 array_push($to_delete, $row['img_idx'], ',');
3595 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3597 $this->cfg_db->db_exec("
3599 WHERE img_idx IN (". implode($to_delete) .")
3602 } // cleanup_phpfspot_db()
3605 * return first image of the page, the $current photo
3608 * this function is used to find out the first photo of the
3609 * current page, in which the $current photo lies. this is
3610 * used to display the correct photo, when calling showPhotoIndex()
3612 * @param integer $current
3613 * @param integer $max
3616 private function getCurrentPage($current, $max)
3618 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3619 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3620 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3626 } // getCurrentPage()
3631 * this function tries to find out the correct mime-type
3632 * for the provided file.
3633 * @param string $file
3636 public function get_mime_info($file)
3638 $details = getimagesize($file);
3640 /* if getimagesize() returns empty, try at least to find out the
3643 if(empty($details) && function_exists('mime_content_type')) {
3645 // mime_content_type is marked as deprecated in the documentation,
3646 // but is it really necessary to force users to install a PECL
3648 $details['mime'] = mime_content_type($file);
3651 return $details['mime'];
3653 } // get_mime_info()
3656 * return tag-name by tag-idx
3658 * this function returns the tag-name for the requested
3659 * tag specified by tag-idx.
3660 * @param integer $idx
3663 public function get_tag_name($idx)
3665 if($result = $this->db->db_fetchSingleRow("
3669 id LIKE '". $idx ."'")) {
3671 return $result['name'];
3680 * parse user friendly url which got rewritten by the websever
3681 * @param string $request_uri
3684 private function parse_user_friendly_url($request_uri)
3686 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3688 $options = explode('/', $request_uri);
3690 switch($options[1]) {
3692 if(is_numeric($options[2])) {
3693 $this->session_cleanup();
3694 //unset($_SESSION['start_action']);
3695 //unset($_SESSION['selected_tags']);
3696 $_GET['mode'] = 'showp';
3697 return $this->showPhoto($options[2]);
3701 if(is_numeric($options[2])) {
3704 if(isset($options[3]) && is_numeric($options[3]))
3705 $width = $options[3];
3706 if(isset($options[4]) && is_numeric($options[4]))
3707 $version = $options[4];
3708 require_once "phpfspot_img.php";
3709 $img = new PHPFSPOT_IMG;
3710 $img->showImg($options[2], $width, $version);
3715 if(is_numeric($options[2])) {
3716 $this->session_cleanup();
3717 $_GET['tags'] = $options[2];
3718 $_SESSION['selected_tags'] = Array($options[2]);
3719 if(isset($options[3]) && is_numeric($options[3]))
3720 $_SESSION['begin_with'] = $options[3];
3721 return $this->showPhotoIndex();
3727 } // parse_user_friendly_url()
3730 * check if user-friendly-urls are enabled
3732 * this function will return true, if the config option
3733 * $user_friendly_url has been set. Otherwise false.
3736 private function is_user_friendly_url()
3738 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3743 } // is_user_friendly_url()
3748 * this function will cleanup user's session information
3750 private function session_cleanup()
3752 unset($_SESSION['begin_with']);
3753 $this->resetDateSearch();
3754 $this->resetPhotoView();
3755 $this->resetTagSearch();
3756 $this->resetNameSearch();
3757 $this->resetDateSearch();
3760 } // session_cleanup()
3763 * get database version
3765 * this function queries the meta table
3766 * and returns the current database version.
3770 public function get_db_version()
3772 if($row = $this->cfg_db->db_fetchSingleRow("
3773 SELECT meta_value as meta_value
3777 meta_key LIKE 'phpfspot Database Version'
3780 return $row['meta_value'];
3786 } // get_db_version()
3789 * get photo versions
3791 * this function returns an array of all available
3792 * alterntaive versions of the provided photo id.
3793 * has alternative photo versions available
3798 public function get_photo_versions($idx)
3800 $versions = Array();
3802 $result = $this->db->db_query("
3808 photo_id LIKE '". $idx ."'");
3810 while($row = $this->cfg_db->db_fetch_object($result)) {
3811 array_push($versions, $row['version_id']);
3816 } // get_photo_versions()
3819 * check for invalid version of photo
3821 * this function validates the provided photo-id and version-id
3823 * @param int $photo_idx
3824 * @param int $version_idx
3827 public function is_valid_version($photo_idx, $version_idx)
3829 /* the original version is always valid */
3830 if($version_idx == 0)
3833 if($versions = $this->get_photo_versions($photo_idx)) {
3834 if(in_array($version_idx, $versions))
3840 } // is_valid_version()
3843 * get photo version name
3845 * this function returns the name of the version
3846 * identified by the photo-id and version-id.
3848 * @param int $photo_idx
3849 * @param int $version_idx
3852 public function get_photo_version_name($photo_idx, $version_idx)
3854 if($row = $this->db->db_fetchSingleRow("
3860 photo_id LIKE '". $photo_idx ."'
3862 version_id LIKE '". $version_idx ."'")) {
3864 return $row['name'];
3870 } // get_photo_version_name()
3874 public function is_valid_width($image_width)
3876 if(in_array($image_width,
3877 Array($this->cfg->thumb_width,
3878 $this->cfg->photo_width,
3879 $this->cfg->mini_width,
3886 } // is_valid_width()