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 is writeable */
145 if(!is_writeable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
147 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
148 ." speed up some database operations.\n";
152 /* open the database */
153 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
155 /* change sqlite temp directory, if requested */
156 if(isset($this->cfg->sqlite_temp_dir)) {
159 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
163 /* get F-Spot database version */
164 $this->dbver = $this->getFspotDBVersion();
166 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
167 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
171 if(!is_writeable($this->cfg->thumb_path)) {
172 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
176 /******* Opening phpfspot's sqlite database *********/
178 /* Check if directory where the database file is stored is writeable */
179 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
180 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
181 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
185 /* Check if database file is writeable */
186 if(!is_writeable($this->cfg->phpfspot_db)) {
187 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
188 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
192 /* open the database */
193 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
195 /* change sqlite temp directory, if requested */
196 if(isset($this->cfg->sqlite_temp_dir)) {
197 $this->cfg_db->db_exec("
199 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
203 /* Check if some tables need to be created */
204 $this->check_phpfspot_db();
206 /* overload Smarty class with our own template handler */
207 require_once "phpfspot_tmpl.php";
208 $this->tmpl = new PHPFSPOT_TMPL();
210 $this->tmpl->assign('web_path', $this->cfg->web_path);
212 /* Starting with F-Spot 0.4.2, the rating-feature was available */
213 if($this->dbver > 10) {
214 $this->tmpl->assign('has_rating', true);
215 $this->sort_orders = array_merge($this->sort_orders, array(
216 'rate_asc' => 'Rate ↑',
217 'rate_desc' => 'Rate ↓',
221 /* check if all necessary indices exist */
222 $this->checkDbIndices();
224 /* if session is not yet started, do it now */
225 if(session_id() == "")
228 if(!isset($_SESSION['tag_condition']))
229 $_SESSION['tag_condition'] = 'or';
231 /* if sort-order has not been set yet, get the one specified in the config */
232 if(!isset($_SESSION['sort_order']))
233 $_SESSION['sort_order'] = $this->cfg->sort_order;
235 if(!isset($_SESSION['searchfor_tag']))
236 $_SESSION['searchfor_tag'] = '';
238 // if begin_with is still set but thumbs_per_page is now 0, unset it
239 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
240 unset($_SESSION['begin_with']);
242 // if user-friendly-url's are enabled, set also a flag for the template handler
243 if($this->is_user_friendly_url()) {
244 $this->tmpl->assign('user_friendly_url', 'true');
249 public function __destruct()
255 * show - generate html output
257 * this function can be called after the constructor has
258 * prepared everyhing. it will load the index.tpl smarty
259 * template. if necessary it will registere pre-selects
260 * (photo index, photo, tag search, date search) into
263 public function show()
265 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
266 $this->tmpl->assign('page_title', $this->cfg->page_title);
267 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
268 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
271 if($this->is_user_friendly_url()) {
272 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
275 if(isset($_GET['mode'])) {
277 $_SESSION['start_action'] = $_GET['mode'];
279 switch($_GET['mode']) {
281 if(isset($_GET['tags'])) {
282 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
284 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
285 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
287 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
288 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
292 if(isset($_GET['tags'])) {
293 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
294 $_SESSION['start_action'] = 'showp';
296 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
297 if($_SESSION['current_photo'] != $_GET['id'])
298 unset($_SESSION['current_version']);
299 $_SESSION['current_photo'] = $_GET['id'];
300 $_SESSION['start_action'] = 'showp';
302 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
303 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
305 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
306 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
310 /* fetch export template */
311 print $this->tmpl->fetch("export.tpl");
312 /* no further execution necessary. */
316 /* fetch slideshow template */
317 print $this->tmpl->show("slideshow.tpl");
318 /* no further execution necessary. */
322 if(isset($_GET['tags'])) {
323 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
325 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
326 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
328 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
329 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
337 /* if date-search variables are registered in the session, set the check
338 for "consider date-range" in the html output
340 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
341 $this->tmpl->assign('date_search_enabled', true);
343 /* if rate-search variables are registered in the session, set the check
344 for "consider rate-range" in the html output
346 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
347 $this->tmpl->assign('rate_search_enabled', true);
350 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
351 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
352 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
354 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
355 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
356 $this->tmpl->assign('rate_search', $this->get_rate_search());
358 /* if no site-content has been set yet... */
359 if(!isset($content)) {
360 /* if tags are already selected, we can immediately display photo-index */
361 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
362 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
363 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
364 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
366 /* if a photo is already selected, we can immediately display single-photo */
367 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
368 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
370 /* ok, then let us show the welcome page... */
371 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
376 $this->tmpl->assign('initial_content', $content);
378 $this->tmpl->show("index.tpl");
383 * get_tags - grab all tags of f-spot's database
385 * this function will get all available tags from
386 * the f-spot database and store them within two
387 * arrays within this class for later usage. in
388 * fact, if the user requests (hide_tags) it will
389 * opt-out some of them.
391 * this function is getting called once by show()
393 private function get_tags()
395 $this->avail_tags = Array();
398 /* if show_tags has been set in the configuration (only show photos
399 which are tagged by these tags) they following will take care,
400 that only these other tags are displayed where the photo is also
401 tagged with one of show_tags.
403 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
406 DISTINCT t1.id as id, t1.name as name
409 INNER JOIN photo_tags
410 pt2 ON pt1.photo_id=pt2.photo_id
416 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
418 t1.sort_priority ASC";
420 $result = $this->db->db_query($query_str);
424 $result = $this->db->db_query("
427 ORDER BY sort_priority ASC
431 while($row = $this->db->db_fetch_object($result)) {
433 $tag_id = $row['id'];
434 $tag_name = $row['name'];
436 /* if the user has specified to ignore this tag in phpfspot's
437 configuration, ignore it here so it does not get added to
440 if(in_array($row['name'], $this->cfg->hide_tags))
443 /* if you include the following if-clause and the user has specified
444 to only show certain tags which are specified in phpfspot's
445 configuration, ignore all others so they will not be added to the
447 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
448 !in_array($row['name'], $this->cfg->show_tags))
452 $this->tags[$tag_id] = $tag_name;
453 $this->avail_tags[$count] = $tag_id;
461 * get all photo details from F-Spot database
463 * this function queries the F-Spot database for all available
464 * details of the requested photo. It returns them as a object.
466 * Furthermore it takes care of the photo version to be requested.
467 * If photo version is not yet, it queries information for the
470 * @param integer $idx
471 * @return object|null
473 public function get_photo_details($idx, $version_idx = NULL)
475 /* ~ F-Spot version 0.3.x */
476 if($this->dbver < 9) {
478 SELECT p.id, p.name, p.time, p.directory_path, p.description
483 /* till F-Spot version 0.4.1 */
484 if($this->dbver < 11) {
486 SELECT p.id, p.uri, p.time, p.description
491 /* rating value got introduced */
493 SELECT p.id, p.uri, p.time, p.description, p.rating
499 /* if show_tags is set, only return details of photos which are
500 tagged with a tag that has been specified to be shown.
502 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
504 INNER JOIN photo_tags pt
508 WHERE p.id='". $idx ."'
509 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
513 WHERE p.id='". $idx ."'
517 if($row = $this->db->db_fetchSingleRow($query_str)) {
519 /* before F-Spot db version 9 there was no uri column but
520 seperated fields for directory_path and name (= filename).
522 if($this->dbver < 9) {
523 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
526 /* if version-idx has not yet been set, get the latest photo version */
527 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
528 $version_idx = $this->get_latest_version($idx);
530 /* if an alternative version has been requested */
531 if($version_idx > 0) {
533 /* check for alternative versions */
534 if($version = $this->db->db_fetchSingleRow("
536 version_id, name, uri
540 photo_id LIKE '". $idx ."'
542 version_id LIKE '". $version_idx ."'
545 $row['name'] = $version['name'];
546 $row['uri'] = $version['uri'];
557 } // get_photo_details()
560 * returns aligned photo names
562 * this function returns aligned (length) names for a specific photo.
563 * If the length of the name exceeds $limit the name will bei
566 * @param integer $idx
567 * @param integer $limit
568 * @return string|null
570 public function getPhotoName($idx, $limit = 0)
572 if($details = $this->get_photo_details($idx)) {
573 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
574 $name = $this->shrink_text($long_name, $limit);
584 * get photo rating level
586 * this function will return the integer-based rating level of a
587 * photo. This can only be done, if the F-Spot database is at a
588 * specific version. If rating value can not be found, zero will
589 * be returned indicating no rating value is available.
594 public function get_photo_rating($idx)
596 if($detail = $this->get_photo_details($idx)) {
597 if(isset($detail['rating']))
598 return $detail['rating'];
603 } // get_photo_rating()
606 * get rate-search bars
608 * this function will return the rating-bars for the search field.
612 public function get_rate_search()
616 for($i = 1; $i <= 5; $i++) {
618 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
620 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
621 $bar.= $this->cfg->web_path ."/resources/star.png";
623 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
626 onmouseover=\"show_rate('from', ". $i .");\"
627 onmouseout=\"reset_rate('from');\"
628 onclick=\"set_rate('from', ". $i .")\" />";
633 for($i = 1; $i <= 5; $i++) {
635 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
637 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
638 $bar.= $this->cfg->web_path ."/resources/star.png";
640 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
643 onmouseover=\"show_rate('to', ". $i .");\"
644 onmouseout=\"reset_rate('to');\"
645 onclick=\"set_rate('to', ". $i .");\" />";
650 } // get_rate_search()
653 * shrink text according provided limit
655 * If the length of the name exceeds $limit, text will be shortend
656 * and inner content will be replaced with "...".
659 * @param integer $limit
662 private function shrink_text($text, $limit)
664 if($limit != 0 && strlen($text) > $limit) {
665 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
673 * translate f-spoth photo path
675 * as the full-qualified path recorded in the f-spot database
676 * is usally not the same as on the webserver, this function
677 * will replace the path with that one specified in the cfg
678 * @param string $path
681 public function translate_path($path)
683 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
688 * control HTML ouput for a single photo
690 * this function provides all the necessary information
691 * for the single photo template.
692 * @param integer photo
694 public function showPhoto($photo)
696 /* get all photos from the current photo selection */
697 $all_photos = $this->getPhotoSelection();
698 $count = count($all_photos);
700 for($i = 0; $i < $count; $i++) {
702 // $get_next will be set, when the photo which has to
703 // be displayed has been found - this means that the
704 // next available is in fact the NEXT image (for the
706 if(isset($get_next)) {
707 $next_img = $all_photos[$i];
711 /* the next photo is our NEXT photo */
712 if($all_photos[$i] == $photo) {
716 $previous_img = $all_photos[$i];
719 if($photo == $all_photos[$i]) {
724 $details = $this->get_photo_details($photo);
731 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
733 /* if current version is already set, use it */
734 if($this->get_current_version() !== false)
735 $version = $this->get_current_version();
737 /* if version not set yet, we assume to display the latest version */
738 if(!isset($version) || !$this->is_valid_version($photo, $version))
739 $version = $this->get_latest_version($photo);
741 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
743 if(!file_exists($orig_path)) {
744 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
748 if(!is_readable($orig_path)) {
749 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
753 /* If the thumbnail doesn't exist yet, try to create it */
754 if(!file_exists($thumb_path)) {
755 $this->gen_thumb($photo, true);
756 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
759 /* get mime-type, height and width from the original photo */
760 $info = getimagesize($orig_path);
762 /* get EXIF information if JPEG */
763 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
764 $meta = $this->get_meta_informations($orig_path);
767 /* If EXIF data are available, use them */
768 if(isset($meta['ExifImageWidth'])) {
769 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
771 $meta_res = $info[0] ."x". $info[1];
774 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
775 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
777 $extern_link = "index.php?mode=showp&id=". $photo;
778 $current_tags = $this->getCurrentTags();
779 if($current_tags != "") {
780 $extern_link.= "&tags=". $current_tags;
782 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
783 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
786 $this->tmpl->assign('extern_link', $extern_link);
788 if(!file_exists($thumb_path)) {
789 $this->_error("Can't open file ". $thumb_path ."\n");
793 $info_thumb = getimagesize($thumb_path);
795 $this->tmpl->assign('description', $details['description']);
796 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
797 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
799 $this->tmpl->assign('width', $info_thumb[0]);
800 $this->tmpl->assign('height', $info_thumb[1]);
801 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
802 $this->tmpl->assign('ExifMadeWith', $meta_make);
803 $this->tmpl->assign('ExifOrigResolution', $meta_res);
804 $this->tmpl->assign('ExifFileSize', $meta_size);
806 if($this->is_user_friendly_url()) {
807 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
808 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
811 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
812 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
815 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
817 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
818 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
819 $this->tmpl->assign('current_img', $photo);
821 if(isset($previous_img)) {
822 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
823 $this->tmpl->assign('prev_img', $previous_img);
826 if(isset($next_img)) {
827 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
828 $this->tmpl->assign('next_img', $next_img);
831 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
832 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
833 $this->tmpl->assign('photo_number', $i);
834 $this->tmpl->assign('photo_count', count($all_photos));
835 $this->tmpl->assign('photo', $photo);
836 $this->tmpl->assign('version', $version);
838 /* if the photo as alternative versions, set a flag for the template */
839 if($this->get_photo_versions($photo))
840 $this->tmpl->assign('has_versions', true);
842 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
844 return $this->tmpl->fetch("single_photo.tpl");
849 * all available tags and tag cloud
851 * this function outputs all available tags (time ordered)
852 * and in addition output them as tag cloud (tags which have
853 * many photos will appears more then others)
855 public function getAvailableTags()
857 /* retrive tags from database */
862 $result = $this->db->db_query("
863 SELECT tag_id as id, count(tag_id) as quantity
873 while($row = $this->db->db_fetch_object($result)) {
874 $tags[$row['id']] = $row['quantity'];
877 // change these font sizes if you will
878 $max_size = 125; // max font size in %
879 $min_size = 75; // min font size in %
882 $max_sat = hexdec('cc');
883 $min_sat = hexdec('44');
885 // get the largest and smallest array values
886 $max_qty = max(array_values($tags));
887 $min_qty = min(array_values($tags));
889 // find the range of values
890 $spread = $max_qty - $min_qty;
891 if (0 == $spread) { // we don't want to divide by zero
895 // determine the font-size increment
896 // this is the increase per tag quantity (times used)
897 $step = ($max_size - $min_size)/($spread);
898 $step_sat = ($max_sat - $min_sat)/($spread);
900 // loop through our tag array
901 foreach ($tags as $key => $value) {
903 /* has the currently processed tag already been added to
904 the selected tag list? if so, ignore it here...
906 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
909 // calculate CSS font-size
910 // find the $value in excess of $min_qty
911 // multiply by the font-size increment ($size)
912 // and add the $min_size set above
913 $size = $min_size + (($value - $min_qty) * $step);
914 // uncomment if you want sizes in whole %:
917 $color = $min_sat + ($value - $min_qty) * $step_sat;
923 if(isset($this->tags[$key])) {
924 if($this->is_user_friendly_url()) {
925 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
926 onclick=\"Tags('add', ". $key ."); return false;\"
928 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
929 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
932 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
933 onclick=\"Tags('add', ". $key ."); return false;\"
935 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
936 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
941 $output = substr($output, 0, strlen($output)-2);
944 } // getAvailableTags()
947 * output all selected tags
949 * this function output all tags which have been selected
950 * by the user. the selected tags are stored in the
951 * session-variable $_SESSION['selected_tags']
954 public function getSelectedTags($type = 'link')
956 /* retrive tags from database */
961 foreach($this->avail_tags as $tag)
963 // return all selected tags
964 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
969 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
973 <div class=\"tagresulttag\">
974 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
975 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
985 $output = substr($output, 0, strlen($output)-2);
989 return "no tags selected";
992 } // getSelectedTags()
995 * add tag to users session variable
997 * this function will add the specified to users current
998 * tag selection. if a date search has been made before
999 * it will be now cleared
1002 public function addTag($tag)
1004 if(!isset($_SESSION['selected_tags']))
1005 $_SESSION['selected_tags'] = Array();
1007 if(isset($_SESSION['searchfor_tag']))
1008 unset($_SESSION['searchfor_tag']);
1010 // has the user requested to hide this tag, and still someone,
1011 // somehow tries to add it, don't allow this.
1012 if(!isset($this->cfg->hide_tags) &&
1013 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1016 if(!in_array($tag, $_SESSION['selected_tags']))
1017 array_push($_SESSION['selected_tags'], $tag);
1024 * remove tag to users session variable
1026 * this function removes the specified tag from
1027 * users current tag selection
1028 * @param string $tag
1031 public function delTag($tag)
1033 if(isset($_SESSION['searchfor_tag']))
1034 unset($_SESSION['searchfor_tag']);
1036 if(isset($_SESSION['selected_tags'])) {
1037 $key = array_search($tag, $_SESSION['selected_tags']);
1038 unset($_SESSION['selected_tags'][$key]);
1039 sort($_SESSION['selected_tags']);
1047 * reset tag selection
1049 * if there is any tag selection, it will be
1052 public function resetTags()
1054 if(isset($_SESSION['selected_tags']))
1055 unset($_SESSION['selected_tags']);
1060 * returns the value for the autocomplete tag-search
1063 public function get_xml_tag_list()
1065 if(!isset($_GET['search']) || !is_string($_GET['search']))
1066 $_GET['search'] = '';
1071 /* retrive tags from database */
1074 $matched_tags = Array();
1076 header("Content-Type: text/xml");
1078 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1079 $string.= "<results>\n";
1081 foreach($this->avail_tags as $tag)
1083 if(!empty($_GET['search']) &&
1084 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1085 count($matched_tags) < $length) {
1087 $count = $this->get_num_photos($tag);
1090 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1093 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1099 /* if we have collected enough items, break out */
1100 if(count($matched_tags) >= $length)
1104 $string.= "</results>\n";
1108 } // get_xml_tag_list()
1112 * reset single photo
1114 * if a specific photo was requested (external link)
1115 * unset the session variable now
1117 public function resetPhotoView()
1119 if(isset($_SESSION['current_photo']))
1120 unset($_SESSION['current_photo']);
1122 if(isset($_SESSION['current_version']))
1123 unset($_SESSION['current_version']);
1125 } // resetPhotoView();
1130 * if any tag search has taken place, reset it now
1132 public function resetTagSearch()
1134 if(isset($_SESSION['searchfor_tag']))
1135 unset($_SESSION['searchfor_tag']);
1137 } // resetTagSearch()
1142 * if any name search has taken place, reset it now
1144 public function resetNameSearch()
1146 if(isset($_SESSION['searchfor_name']))
1147 unset($_SESSION['searchfor_name']);
1149 } // resetNameSearch()
1154 * if any date search has taken place, reset it now.
1156 public function resetDateSearch()
1158 if(isset($_SESSION['from_date']))
1159 unset($_SESSION['from_date']);
1160 if(isset($_SESSION['to_date']))
1161 unset($_SESSION['to_date']);
1163 } // resetDateSearch();
1168 * if any rate search has taken place, reset it now.
1170 public function resetRateSearch()
1172 if(isset($_SESSION['rate_from']))
1173 unset($_SESSION['rate_from']);
1174 if(isset($_SESSION['rate_to']))
1175 unset($_SESSION['rate_to']);
1177 } // resetRateSearch();
1180 * return all photo according selection
1182 * this function returns all photos based on
1183 * the tag-selection, tag- or date-search.
1184 * the tag-search also has to take care of AND
1185 * and OR conjunctions
1188 public function getPhotoSelection()
1190 $matched_photos = Array();
1191 $additional_where_cond = "";
1193 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1194 $from_date = $_SESSION['from_date'];
1195 $to_date = $_SESSION['to_date'];
1196 $additional_where_cond.= "
1197 p.time>='". $from_date ."'
1199 p.time<='". $to_date ."'
1203 if(isset($_SESSION['searchfor_name'])) {
1205 /* check for previous conditions. if so add 'AND' */
1206 if(!empty($additional_where_cond)) {
1207 $additional_where_cond.= " AND ";
1210 if($this->dbver < 9) {
1211 $additional_where_cond.= "
1213 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1215 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1220 $additional_where_cond.= "
1222 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1224 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1230 /* limit result based on rate-search */
1231 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1233 if($this->dbver > 10) {
1235 /* check for previous conditions. if so add 'AND' */
1236 if(!empty($additional_where_cond)) {
1237 $additional_where_cond.= " AND ";
1240 $additional_where_cond.= "
1241 p.rating >= ". $_SESSION['rate_from'] ."
1243 p.rating <= ". $_SESSION['rate_to'] ."
1248 if(isset($_SESSION['sort_order'])) {
1249 $order_str = $this->get_sort_order();
1252 /* return a search result */
1253 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1255 SELECT DISTINCT pt1.photo_id
1257 INNER JOIN photo_tags pt2
1258 ON pt1.photo_id=pt2.photo_id
1262 ON pt1.photo_id=p.id
1265 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1267 if(!empty($additional_where_cond))
1268 $query_str.= "AND ". $additional_where_cond ." ";
1270 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1271 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1274 if(isset($order_str))
1275 $query_str.= $order_str;
1277 $result = $this->db->db_query($query_str);
1278 while($row = $this->db->db_fetch_object($result)) {
1279 array_push($matched_photos, $row['photo_id']);
1281 return $matched_photos;
1284 /* return according the selected tags */
1285 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1287 foreach($_SESSION['selected_tags'] as $tag)
1288 $selected.= $tag .",";
1289 $selected = substr($selected, 0, strlen($selected)-1);
1291 /* photo has to match at least on of the selected tags */
1292 if($_SESSION['tag_condition'] == 'or') {
1294 SELECT DISTINCT pt1.photo_id
1296 INNER JOIN photo_tags pt2
1297 ON pt1.photo_id=pt2.photo_id
1301 ON pt1.photo_id=p.id
1302 WHERE pt1.tag_id IN (". $selected .")
1304 if(!empty($additional_where_cond))
1305 $query_str.= "AND ". $additional_where_cond ." ";
1307 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1308 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1311 if(isset($order_str))
1312 $query_str.= $order_str;
1314 /* photo has to match all selected tags */
1315 elseif($_SESSION['tag_condition'] == 'and') {
1317 if(count($_SESSION['selected_tags']) >= 32) {
1318 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1319 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1323 /* Join together a table looking like
1325 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1327 so the query can quickly return all images matching the
1328 selected tags in an AND condition
1333 SELECT DISTINCT pt1.photo_id
1337 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1344 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1346 INNER JOIN photo_tags pt". ($i+2) ."
1347 ON pt1.photo_id=pt". ($i+2) .".photo_id
1352 ON pt1.photo_id=p.id
1354 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1355 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1357 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1360 if(!empty($additional_where_cond))
1361 $query_str.= "AND ". $additional_where_cond;
1363 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1364 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1367 if(isset($order_str))
1368 $query_str.= $order_str;
1372 $result = $this->db->db_query($query_str);
1373 while($row = $this->db->db_fetch_object($result)) {
1374 array_push($matched_photos, $row['photo_id']);
1376 return $matched_photos;
1379 /* return all available photos */
1381 SELECT DISTINCT p.id
1383 LEFT JOIN photo_tags pt
1389 if(!empty($additional_where_cond))
1390 $query_str.= "WHERE ". $additional_where_cond ." ";
1392 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1393 if(!empty($additional_where_cond))
1394 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1396 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1399 if(isset($order_str))
1400 $query_str.= $order_str;
1402 $result = $this->db->db_query($query_str);
1403 while($row = $this->db->db_fetch_object($result)) {
1404 array_push($matched_photos, $row['id']);
1406 return $matched_photos;
1408 } // getPhotoSelection()
1411 * control HTML ouput for photo index
1413 * this function provides all the necessary information
1414 * for the photo index template.
1417 public function showPhotoIndex()
1419 $photos = $this->getPhotoSelection();
1420 $current_tags = $this->getCurrentTags();
1422 $count = count($photos);
1424 /* if all thumbnails should be shown on one page */
1425 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1429 /* thumbnails should be splitted up in several pages */
1430 elseif($this->cfg->thumbs_per_page > 0) {
1432 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1436 $begin_with = $_SESSION['begin_with'];
1439 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1443 $images[$thumbs] = Array();
1444 $img_height[$thumbs] = Array();
1445 $img_width[$thumbs] = Array();
1446 $img_id[$thumbs] = Array();
1447 $img_name[$thumbs] = Array();
1448 $img_fullname[$thumbs] = Array();
1449 $img_title = Array();
1450 $img_rating = Array();
1452 for($i = $begin_with; $i < $end_with; $i++) {
1454 if(isset($photos[$i])) {
1456 $images[$thumbs] = $photos[$i];
1457 $img_id[$thumbs] = $i;
1458 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1459 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1460 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1461 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1463 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1465 if(file_exists($thumb_path)) {
1466 $info = getimagesize($thumb_path);
1467 $img_width[$thumbs] = $info[0];
1468 $img_height[$thumbs] = $info[1];
1474 // +1 for for smarty's selection iteration
1477 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1478 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1480 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1481 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1482 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1485 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1486 $this->tmpl->assign('tag_result', 1);
1489 /* do we have to display the page selector ? */
1490 if($this->cfg->thumbs_per_page != 0) {
1494 /* calculate the page switchers */
1495 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1496 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1498 if($begin_with != 0)
1499 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1500 if($end_with < $count)
1501 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1503 $photo_per_page = $this->cfg->thumbs_per_page;
1504 $last_page = ceil($count / $photo_per_page);
1506 /* get the current selected page */
1507 if($begin_with == 0) {
1511 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1518 for($i = 1; $i <= $last_page; $i++) {
1520 if($current_page == $i)
1521 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1522 elseif($current_page-1 == $i || $current_page+1 == $i)
1523 $style = "style=\"font-size: 105%;\"";
1524 elseif(($current_page-5 >= $i) && ($i != 1) ||
1525 ($current_page+5 <= $i) && ($i != $last_page))
1526 $style = "style=\"font-size: 75%;\"";
1530 $start_with = ($i*$photo_per_page)-$photo_per_page;
1532 if($this->is_user_friendly_url()) {
1533 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1536 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1538 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1542 $select.= ">". $i ."</a> ";
1544 // until 9 pages we show the selector from 1-9
1545 if($last_page <= 9) {
1546 $page_select.= $select;
1549 if($i == 1 /* first page */ ||
1550 $i == $last_page /* last page */ ||
1551 $i == $current_page /* current page */ ||
1552 $i == ceil($last_page * 0.25) /* first quater */ ||
1553 $i == ceil($last_page * 0.5) /* half */ ||
1554 $i == ceil($last_page * 0.75) /* third quater */ ||
1555 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1556 (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 */ ||
1557 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1558 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1560 $page_select.= $select;
1568 $page_select.= "......... ";
1573 /* only show the page selector if we have more then one page */
1575 $this->tmpl->assign('page_selector', $page_select);
1578 $extern_link = "index.php?mode=showpi";
1579 $rss_link = "index.php?mode=rss";
1580 if($current_tags != "") {
1581 $extern_link.= "&tags=". $current_tags;
1582 $rss_link.= "&tags=". $current_tags;
1584 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1585 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1586 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1589 $export_link = "index.php?mode=export";
1590 $slideshow_link = "index.php?mode=slideshow";
1592 $this->tmpl->assign('extern_link', $extern_link);
1593 $this->tmpl->assign('slideshow_link', $slideshow_link);
1594 $this->tmpl->assign('export_link', $export_link);
1595 $this->tmpl->assign('rss_link', $rss_link);
1596 $this->tmpl->assign('count', $count);
1597 $this->tmpl->assign('width', $this->cfg->thumb_width);
1598 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1599 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1600 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1601 $this->tmpl->assign('images', $images);
1602 $this->tmpl->assign('img_width', $img_width);
1603 $this->tmpl->assign('img_height', $img_height);
1604 $this->tmpl->assign('img_id', $img_id);
1605 $this->tmpl->assign('img_name', $img_name);
1606 $this->tmpl->assign('img_fullname', $img_fullname);
1607 $this->tmpl->assign('img_title', $img_title);
1608 $this->tmpl->assign('img_rating', $img_rating);
1609 $this->tmpl->assign('thumbs', $thumbs);
1610 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1612 $result = $this->tmpl->fetch("photo_index.tpl");
1614 /* if we are returning to photo index from an photo-view,
1615 scroll the window to the last shown photo-thumbnail.
1616 after this, unset the last_photo session variable.
1618 if(isset($_SESSION['last_photo'])) {
1619 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1620 unset($_SESSION['last_photo']);
1625 } // showPhotoIndex()
1628 * show credit template
1630 public function showCredits()
1632 $this->tmpl->assign('version', $this->cfg->version);
1633 $this->tmpl->assign('product', $this->cfg->product);
1634 $this->tmpl->assign('db_version', $this->dbver);
1635 $this->tmpl->show("credits.tpl");
1640 * create thumbnails for the requested width
1642 * this function creates image thumbnails of $orig_image
1643 * stored as $thumb_image. It will check if the image is
1644 * in a supported format, if necessary rotate the image
1645 * (based on EXIF orientation meta headers) and re-sizing.
1646 * @param string $orig_image
1647 * @param string $thumb_image
1648 * @param integer $width
1651 public function create_thumbnail($orig_image, $thumb_image, $width)
1653 if(!file_exists($orig_image)) {
1657 $mime = $this->get_mime_info($orig_image);
1659 /* check if original photo is a support image type */
1660 if(!$this->checkifImageSupported($mime))
1667 $meta = $this->get_meta_informations($orig_image);
1673 if(isset($meta['Orientation'])) {
1674 switch($meta['Orientation']) {
1675 case 1: /* top, left */
1676 /* nothing to do */ break;
1677 case 2: /* top, right */
1678 $rotate = 0; $flip_hori = true; break;
1679 case 3: /* bottom, left */
1680 $rotate = 180; break;
1681 case 4: /* bottom, right */
1682 $flip_vert = true; break;
1683 case 5: /* left side, top */
1684 $rotate = 90; $flip_vert = true; break;
1685 case 6: /* right side, top */
1686 $rotate = 90; break;
1687 case 7: /* left side, bottom */
1688 $rotate = 270; $flip_vert = true; break;
1689 case 8: /* right side, bottom */
1690 $rotate = 270; break;
1694 $src_img = @imagecreatefromjpeg($orig_image);
1700 $src_img = @imagecreatefrompng($orig_image);
1704 case 'image/x-portable-pixmap':
1706 $src_img = new Imagick($orig_image);
1707 $handler = "imagick";
1712 if(!isset($src_img) || empty($src_img)) {
1713 print "Can't load image from ". $orig_image ."\n";
1721 /* grabs the height and width */
1722 $cur_width = imagesx($src_img);
1723 $cur_height = imagesy($src_img);
1725 // If requested width is more then the actual image width,
1726 // do not generate a thumbnail, instead safe the original
1727 // as thumbnail but with lower quality. But if the image
1728 // is to heigh too, then we still have to resize it.
1729 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1730 $result = imagejpeg($src_img, $thumb_image, 75);
1731 imagedestroy($src_img);
1738 $cur_width = $src_img->getImageWidth();
1739 $cur_height = $src_img->getImageHeight();
1741 // If requested width is more then the actual image width,
1742 // do not generate a thumbnail, instead safe the original
1743 // as thumbnail but with lower quality. But if the image
1744 // is to heigh too, then we still have to resize it.
1745 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1746 $src_img->setCompressionQuality(75);
1747 $src_img->setImageFormat('jpeg');
1748 $src_img->writeImage($thumb_image);
1750 $src_img->destroy();
1757 // If the image will be rotate because EXIF orientation said so
1758 // 'virtually rotate' the image for further calculations
1759 if($rotate == 90 || $rotate == 270) {
1761 $cur_width = $cur_height;
1765 /* calculates aspect ratio */
1766 $aspect_ratio = $cur_height / $cur_width;
1769 if($aspect_ratio < 1) {
1771 $new_h = abs($new_w * $aspect_ratio);
1773 /* 'virtually' rotate the image and calculate it's ratio */
1774 $tmp_w = $cur_height;
1775 $tmp_h = $cur_width;
1776 /* now get the ratio from the 'rotated' image */
1777 $tmp_ratio = $tmp_h/$tmp_w;
1778 /* now calculate the new dimensions */
1780 $tmp_h = abs($tmp_w * $tmp_ratio);
1782 // now that we know, how high they photo should be, if it
1783 // gets rotated, use this high to scale the image
1785 $new_w = abs($new_h / $aspect_ratio);
1787 // If the image will be rotate because EXIF orientation said so
1788 // now 'virtually rotate' back the image for the image manipulation
1789 if($rotate == 90 || $rotate == 270) {
1800 /* creates new image of that size */
1801 $dst_img = imagecreatetruecolor($new_w, $new_h);
1803 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1805 /* copies resized portion of original image into new image */
1806 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1808 /* needs the image to be flipped horizontal? */
1810 $this->_debug("(FLIP)");
1811 $dst_img = $this->flipImage($dst_img, 'hori');
1813 /* needs the image to be flipped vertical? */
1815 $this->_debug("(FLIP)");
1816 $dst_img = $this->flipImage($dst_img, 'vert');
1820 $this->_debug("(ROTATE)");
1821 $dst_img = $this->rotateImage($dst_img, $rotate);
1824 /* write down new generated file */
1825 $result = imagejpeg($dst_img, $thumb_image, 75);
1827 /* free your mind */
1828 imagedestroy($dst_img);
1829 imagedestroy($src_img);
1831 if($result === false) {
1832 print "Can't write thumbnail ". $thumb_image ."\n";
1842 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1844 /* needs the image to be flipped horizontal? */
1846 $this->_debug("(FLIP)");
1847 $src_img->rotateImage(new ImagickPixel(), 90);
1848 $src_img->flipImage();
1849 $src_img->rotateImage(new ImagickPixel(), -90);
1851 /* needs the image to be flipped vertical? */
1853 $this->_debug("(FLIP)");
1854 $src_img->flipImage();
1858 $this->_debug("(ROTATE)");
1859 $src_img->rotateImage(new ImagickPixel(), $rotate);
1862 $src_img->setCompressionQuality(75);
1863 $src_img->setImageFormat('jpeg');
1865 if(!$src_img->writeImage($thumb_image)) {
1866 print "Can't write thumbnail ". $thumb_image ."\n";
1871 $src_img->destroy();
1878 } // create_thumbnail()
1881 * return all exif meta data from the file
1882 * @param string $file
1885 public function get_meta_informations($file)
1887 return exif_read_data($file);
1889 } // get_meta_informations()
1892 * create phpfspot own sqlite database
1894 * this function creates phpfspots own sqlite database
1895 * if it does not exist yet. this own is used to store
1896 * some necessary informations (md5 sum's, ...).
1898 public function check_phpfspot_db()
1900 // if the config table doesn't exist yet, create it
1901 if(!$this->cfg_db->db_check_table_exists("images")) {
1902 $this->cfg_db->db_exec("
1903 CREATE TABLE images (
1905 img_version_idx int,
1906 img_md5 varchar(32),
1907 UNIQUE(img_idx, img_version_idx)
1912 if(!$this->cfg_db->db_check_table_exists("meta")) {
1913 $this->cfg_db->db_exec("
1915 meta_key varchar(255),
1916 meta_value varchar(255)
1920 /* db_version was added with phpfspot 1.7, before changes
1921 on the phpfspot database where not necessary.
1924 $this->cfg_db->db_exec("
1926 meta_key, meta_value
1928 'phpfspot Database Version',
1929 '". $this->cfg->db_version ."'
1934 /* if version <= 2 and column img_version_idx does not exist yet */
1935 if($this->get_db_version() <= 2 &&
1936 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
1938 if(!$this->cfg_db->db_start_transaction())
1939 die("Can not start database transaction");
1941 $result = $this->cfg_db->db_exec("
1942 CREATE TEMPORARY TABLE images_temp (
1944 img_version_idx int,
1945 img_md5 varchar(32),
1946 UNIQUE(img_idx, img_version_idx)
1951 $this->cfg_db->db_rollback_transaction();
1952 die("Upgrade failed - transaction rollback");
1955 $result = $this->cfg_db->db_exec("
1956 INSERT INTO images_temp
1965 $this->cfg_db->db_rollback_transaction();
1966 die("Upgrade failed - transaction rollback");
1969 $result = $this->cfg_db->db_exec("
1974 $this->cfg_db->db_rollback_transaction();
1975 die("Upgrade failed - transaction rollback");
1978 $result = $this->cfg_db->db_exec("
1979 CREATE TABLE images (
1981 img_version_idx int,
1982 img_md5 varchar(32),
1983 UNIQUE(img_idx, img_version_idx)
1988 $this->cfg_db->db_rollback_transaction();
1989 die("Upgrade failed - transaction rollback");
1992 $result = $this->cfg_db->db_exec("
1999 $this->cfg_db->db_rollback_transaction();
2000 die("Upgrade failed - transaction rollback");
2003 $result = $this->cfg_db->db_exec("
2004 DROP TABLE images_temp
2008 $this->cfg_db->db_rollback_transaction();
2009 die("Upgrade failed - transaction rollback");
2012 if(!$this->cfg_db->db_commit_transaction())
2013 die("Can not commit database transaction");
2017 } // check_phpfspot_db
2020 * generates thumbnails
2022 * This function generates JPEG thumbnails from
2023 * provided F-Spot photo indize and its alternative
2026 * 1. Check if all thumbnail generations (width) are already in place and
2028 * 2. Check if the md5sum of the original file has changed
2029 * 3. Generate the thumbnails if needed
2030 * @param integer $idx
2031 * @param integer $force
2032 * @param boolean $overwrite
2034 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2037 $versions = Array(0);
2039 $resolutions = Array(
2040 $this->cfg->thumb_width,
2041 $this->cfg->photo_width,
2042 $this->cfg->mini_width,
2046 if($alt_versions = $this->get_photo_versions($idx))
2047 $versions = array_merge($versions, $alt_versions);
2049 foreach($versions as $version) {
2051 /* get details from F-Spot's database */
2052 $details = $this->get_photo_details($idx, $version);
2054 /* calculate file MD5 sum */
2055 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2057 if(!file_exists($full_path)) {
2058 $this->_error("File ". $full_path ." does not exist\n");
2062 if(!is_readable($full_path)) {
2063 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2067 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2069 /* If Nikon NEF format, we need to treat it another way */
2070 if(isset($this->cfg->dcraw_bin) &&
2071 file_exists($this->cfg->dcraw_bin) &&
2072 is_executable($this->cfg->dcraw_bin) &&
2073 preg_match('/\.nef$/i', $details['uri'])) {
2075 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2077 /* if PPM file does not exist, let dcraw convert it from NEF */
2078 if(!file_exists($ppm_path)) {
2079 system($this->cfg->dcraw_bin ." -a ". $full_path);
2082 /* for now we handle the PPM instead of the NEF */
2083 $full_path = $ppm_path;
2087 $file_md5 = md5_file($full_path);
2090 foreach($resolutions as $resolution) {
2092 $generate_it = false;
2094 $thumb_sub_path = substr($file_md5, 0, 2);
2095 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2097 /* if thumbnail-subdirectory does not exist yet, create it */
2098 if(!file_exists(dirname($thumb_path))) {
2099 mkdir(dirname($thumb_path), 0755);
2102 /* if the thumbnail file doesn't exist, create it */
2103 if(!file_exists($thumb_path) || $force) {
2104 $generate_it = true;
2106 elseif($file_md5 != $this->getMD5($idx, $version)) {
2107 $generate_it = true;
2110 if($generate_it || $overwrite) {
2112 $this->_debug(" ". $resolution ."px");
2113 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2121 $this->_debug(" already exist");
2124 /* set the new/changed MD5 sum for the current photo */
2126 $this->setMD5($idx, $file_md5, $version);
2129 $this->_debug("\n");
2136 * returns stored md5 sum for a specific photo
2138 * this function queries the phpfspot database for a stored MD5
2139 * checksum of the specified photo. It also takes care of the
2140 * requested photo version - original or alternative photo.
2142 * @param integer $idx
2143 * @return string|null
2145 public function getMD5($idx, $version_idx = 0)
2147 $result = $this->cfg_db->db_query("
2153 img_idx='". $idx ."'
2155 img_version_idx='". $version_idx ."'
2161 if($img = $this->cfg_db->db_fetch_object($result))
2162 return $img['img_md5'];
2169 * set MD5 sum for the specific photo
2170 * @param integer $idx
2171 * @param string $md5
2173 private function setMD5($idx, $md5, $version_idx = 0)
2175 $result = $this->cfg_db->db_exec("
2176 INSERT OR REPLACE INTO images (
2177 img_idx, img_version_idx, img_md5
2180 '". $version_idx ."',
2188 * store current tag condition
2190 * this function stores the current tag condition
2191 * (AND or OR) in the users session variables
2192 * @param string $mode
2195 public function setTagCondition($mode)
2197 $_SESSION['tag_condition'] = $mode;
2201 } // setTagCondition()
2204 * invoke tag & date search
2206 * this function will return all matching tags and store
2207 * them in the session variable selected_tags. furthermore
2208 * it also handles the date search.
2209 * getPhotoSelection() will then only return the matching
2213 public function startSearch()
2216 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2217 $date_from = $_POST['date_from'];
2219 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2220 $date_to = $_POST['date_to'];
2223 /* tag-name search */
2224 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2225 $searchfor_tag = $_POST['for_tag'];
2226 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2229 unset($_SESSION['searchfor_tag']);
2232 /* file-name search */
2233 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2234 $_SESSION['searchfor_name'] = $_POST['for_name'];
2237 unset($_SESSION['searchfor_name']);
2241 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2243 $_SESSION['rate_from'] = $_POST['rate_from'];
2245 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2246 $_SESSION['rate_to'] = $_POST['rate_to'];
2250 /* delete any previously set value */
2251 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2256 if(isset($date_from) && !empty($date_from))
2257 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2259 unset($_SESSION['from_date']);
2261 if(isset($date_to) && !empty($date_to))
2262 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2264 unset($_SESSION['to_date']);
2266 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2267 /* new search, reset the current selected tags */
2268 $_SESSION['selected_tags'] = Array();
2269 foreach($this->avail_tags as $tag) {
2270 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2271 array_push($_SESSION['selected_tags'], $tag);
2280 * updates sort order in session variable
2282 * this function is invoked by RPC and will sort the requested
2283 * sort order in the session variable.
2284 * @param string $sort_order
2287 public function updateSortOrder($order)
2289 if(isset($this->sort_orders[$order])) {
2290 $_SESSION['sort_order'] = $order;
2294 return "unkown error";
2296 } // updateSortOrder()
2299 * update photo version in session variable
2301 * this function is invoked by RPC and will set the requested
2302 * photo version in the session variable.
2303 * @param string $photo_version
2306 public function update_photo_version($photo_idx, $photo_version)
2308 if($this->is_valid_version($photo_idx, $photo_version)) {
2309 $_SESSION['current_version'] = $photo_version;
2313 return "incorrect photo version provided";
2315 } // update_photo_version()
2320 * this function rotates the image according the
2322 * @param string $img
2323 * @param integer $degress
2326 private function rotateImage($img, $degrees)
2328 if(function_exists("imagerotate")) {
2329 $img = imagerotate($img, $degrees, 0);
2331 function imagerotate($src_img, $angle)
2333 $src_x = imagesx($src_img);
2334 $src_y = imagesy($src_img);
2335 if ($angle == 180) {
2339 elseif ($src_x <= $src_y) {
2343 elseif ($src_x >= $src_y) {
2348 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2349 imagealphablending($rotate, false);
2354 for ($y = 0; $y < ($src_y); $y++) {
2355 for ($x = 0; $x < ($src_x); $x++) {
2356 $color = imagecolorat($src_img, $x, $y);
2357 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2363 for ($y = 0; $y < ($src_y); $y++) {
2364 for ($x = 0; $x < ($src_x); $x++) {
2365 $color = imagecolorat($src_img, $x, $y);
2366 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2372 for ($y = 0; $y < ($src_y); $y++) {
2373 for ($x = 0; $x < ($src_x); $x++) {
2374 $color = imagecolorat($src_img, $x, $y);
2375 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2389 $img = imagerotate($img, $degrees);
2398 * returns flipped image
2400 * this function will return an either horizontal or
2401 * vertical flipped truecolor image.
2402 * @param string $image
2403 * @param string $mode
2406 private function flipImage($image, $mode)
2408 $w = imagesx($image);
2409 $h = imagesy($image);
2410 $flipped = imagecreatetruecolor($w, $h);
2414 for ($y = 0; $y < $h; $y++) {
2415 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2419 for ($x = 0; $x < $w; $x++) {
2420 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2430 * return all assigned tags for the specified photo
2431 * @param integer $idx
2434 private function get_photo_tags($idx)
2436 $result = $this->db->db_query("
2439 INNER JOIN photo_tags pt
2441 WHERE pt.photo_id='". $idx ."'
2446 while($row = $this->db->db_fetch_object($result)) {
2447 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2449 $tags[$row['id']] = $row['name'];
2454 } // get_photo_tags()
2457 * create on-the-fly images with text within
2458 * @param string $txt
2459 * @param string $color
2460 * @param integer $space
2461 * @param integer $font
2464 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2466 if (strlen($color) != 6)
2469 $int = hexdec($color);
2470 $h = imagefontheight($font);
2471 $fw = imagefontwidth($font);
2472 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2473 $lines = count($txt);
2474 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2475 $bg = imagecolorallocate($im, 255, 255, 255);
2476 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2479 foreach ($txt as $text) {
2480 $x = (($w - ($fw * strlen($text))) / 2);
2481 imagestring($im, $font, $x, $y, $text, $color);
2482 $y += ($h + $space);
2485 Header("Content-type: image/png");
2488 } // showTextImage()
2491 * check if all requirements are met
2494 private function check_requirements()
2496 if(!function_exists("imagecreatefromjpeg")) {
2497 print "PHP GD library extension is missing<br />\n";
2501 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2502 print "PHP SQLite3 library extension is missing<br />\n";
2506 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2507 ini_set('track_errors', 1);
2508 @include_once 'HTML/AJAX/Server.php';
2509 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2510 print "PEAR HTML_AJAX package is missing<br />\n";
2513 @include_once 'Calendar/Calendar.php';
2514 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2515 print "PEAR Calendar package is missing<br />\n";
2518 @include_once 'Console/Getopt.php';
2519 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2520 print "PEAR Console_Getopt package is missing<br />\n";
2523 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2524 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2525 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2528 ini_restore('track_errors');
2535 } // check_requirements()
2537 private function _debug($text)
2539 if(isset($this->fromcmd)) {
2546 * check if specified MIME type is supported
2547 * @param string $mime
2550 public function checkifImageSupported($mime)
2552 $supported_types = Array(
2555 "image/x-portable-pixmap",
2559 if(in_array($mime, $supported_types))
2564 } // checkifImageSupported()
2568 * @param string $text
2570 public function _error($text)
2572 switch($this->cfg->logging) {
2575 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2576 print $text ."<br />\n";
2582 error_log($text, 3, $this->cfg->log_file);
2586 $this->runtime_error = true;
2591 * get calendar input-text fields
2593 * this function returns a text-field used for the data selection.
2594 * Either it will be filled with the current date or, if available,
2595 * filled with the date user entered previously.
2597 * @param string $mode
2600 private function get_date_text_field($mode)
2602 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2604 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2606 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2608 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2609 if(!isset($_SESSION[$mode .'_date']))
2610 $output.= " disabled=\"disabled\"";
2615 } // get_date_text_field()
2618 * output calendar matrix
2619 * @param integer $year
2620 * @param integer $month
2621 * @param integer $day
2623 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2625 if (!isset($year)) $year = date('Y');
2626 if (!isset($month)) $month = date('m');
2627 if (!isset($day)) $day = date('d');
2632 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2633 require_once CALENDAR_ROOT.'Day.php';
2636 $month = new Calendar_Month_Weekdays($year,$month);
2639 $prevStamp = $month->prevMonth(true);
2640 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2641 $nextStamp = $month->nextMonth(true);
2642 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2644 $selectedDays = array (
2645 new Calendar_Day($year,$month,$day),
2646 new Calendar_Day($year,12,25),
2649 // Build the days in the month
2650 $month->build($selectedDays);
2652 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2653 $this->tmpl->assign('prev_month', $prev);
2654 $this->tmpl->assign('next_month', $next);
2656 while ( $day = $month->fetch() ) {
2658 if(!isset($matrix[$rows]))
2659 $matrix[$rows] = Array();
2663 $dayStamp = $day->thisDay(true);
2664 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2666 // isFirst() to find start of week
2667 if ( $day->isFirst() )
2670 if ( $day->isSelected() ) {
2671 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2672 } else if ( $day->isEmpty() ) {
2673 $string.= "<td> </td>\n";
2675 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2678 // isLast() to find end of week
2679 if ( $day->isLast() )
2680 $string.= "</tr>\n";
2682 $matrix[$rows][$cols] = $string;
2692 $this->tmpl->assign('matrix', $matrix);
2693 $this->tmpl->assign('rows', $rows);
2694 $this->tmpl->show("calendar.tpl");
2696 } // get_calendar_matrix()
2699 * output export page
2700 * @param string $mode
2702 public function getExport($mode)
2704 $pictures = $this->getPhotoSelection();
2705 $current_tags = $this->getCurrentTags();
2707 foreach($pictures as $picture) {
2709 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2710 if($current_tags != "") {
2711 $orig_url.= "&tags=". $current_tags;
2713 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2714 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2717 if($this->is_user_friendly_url()) {
2718 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2721 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2727 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2728 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2732 // "[%pictureurl% %thumbnailurl%]"
2733 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2736 case 'MoinMoinList':
2737 // " * [%pictureurl% %thumbnailurl%]"
2738 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2749 public function getRSSFeed()
2751 Header("Content-type: text/xml; charset=utf-8");
2752 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2755 xmlns:media="http://search.yahoo.com/mrss/"
2756 xmlns:dc="http://purl.org/dc/elements/1.1/"
2759 <title>phpfspot</title>
2760 <description>phpfspot RSS feed</description>
2761 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2762 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2763 <generator>phpfspot</generator>
2766 $pictures = $this->getPhotoSelection();
2767 $current_tags = $this->getCurrentTags();
2769 foreach($pictures as $picture) {
2771 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2772 if($current_tags != "") {
2773 $orig_url.= "&tags=". $current_tags;
2775 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2776 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2779 $details = $this->get_photo_details($picture);
2781 if($this->is_user_friendly_url()) {
2782 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2785 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2788 $thumb_html = htmlspecialchars("
2789 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2791 ". $details['description']);
2793 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2795 /* get EXIF information if JPEG */
2796 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2797 $meta = $this->get_meta_informations($orig_path);
2802 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2803 <link><?php print htmlspecialchars($orig_url); ?></link>
2804 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2805 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2807 <?php print $thumb_html; ?>
2809 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2824 * get all selected tags
2826 * This function will return all selected tags as one string, seperated
2830 private function getCurrentTags()
2833 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2834 foreach($_SESSION['selected_tags'] as $tag)
2835 $current_tags.= $tag .",";
2836 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2838 return $current_tags;
2840 } // getCurrentTags()
2843 * return the current photo
2845 public function get_current_photo()
2847 if(isset($_SESSION['current_photo'])) {
2848 return $_SESSION['current_photo'];
2853 } // get_current_photo()
2856 * current selected photo version
2858 * this function returns the current selected photo version
2859 * from the session variables.
2863 public function get_current_version()
2865 /* if current version is set, return it, if the photo really has that version */
2866 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2867 return $_SESSION['current_version'];
2871 } // get_current_version()
2874 * returns latest available photo version
2876 * this function returns the latested available version
2877 * for the requested photo.
2881 public function get_latest_version($photo_idx)
2883 /* try to get the lasted version for the current photo */
2884 if($versions = $this->get_photo_versions($photo_idx))
2885 return $versions[count($versions)-1];
2887 /* if no alternative version were found, return original version */
2890 } // get_current_version()
2893 * tells the client browser what to do
2895 * this function is getting called via AJAX by the
2896 * client browsers. it will tell them what they have
2897 * to do next. This is necessary for directly jumping
2898 * into photo index or single photo view when the are
2899 * requested with specific URLs
2902 public function whatToDo()
2904 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2906 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2907 return "showpi_tags";
2909 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2916 * return the current process-user
2919 private function getuid()
2921 if($uid = posix_getuid()) {
2922 if($user = posix_getpwuid($uid)) {
2923 return $user['name'];
2932 * photo version select list
2934 * this function returns a HTML select list (drop down)
2935 * to select a alternative photo version of the original photo.
2937 * @param array $params
2938 * @param smarty $smarty
2941 public function smarty_photo_version_select_list($params, &$smarty)
2943 if(!isset($params['photo']) || !isset($params['current']))
2946 $output = "<option value=\"0\">Original</option>";
2947 $versions = $this->get_photo_versions($params['photo']);
2949 foreach($versions as $version) {
2951 $output.= "<option value=\"". $version ."\"";
2952 if($version == $params['current']) {
2953 $output.= " selected=\"selected\"";
2955 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
2960 } // smarty_photo_version_select_list()
2963 * returns a select-dropdown box to select photo index sort parameters
2964 * @param array $params
2965 * @param smarty $smarty
2968 public function smarty_sort_select_list($params, &$smarty)
2972 foreach($this->sort_orders as $key => $value) {
2973 $output.= "<option value=\"". $key ."\"";
2974 if($key == $_SESSION['sort_order']) {
2975 $output.= " selected=\"selected\"";
2977 $output.= ">". $value ."</option>";
2982 } // smarty_sort_select_list()
2985 * returns the currently selected sort order
2988 private function get_sort_order()
2990 switch($_SESSION['sort_order']) {
2992 return " ORDER BY p.time ASC";
2995 return " ORDER BY p.time DESC";
2998 if($this->dbver < 9) {
2999 return " ORDER BY p.name ASC";
3002 return " ORDER BY basename(p.uri) ASC";
3006 if($this->dbver < 9) {
3007 return " ORDER BY p.name DESC";
3010 return " ORDER BY basename(p.uri) DESC";
3014 return " ORDER BY t.name ASC ,p.time ASC";
3017 return " ORDER BY t.name DESC ,p.time ASC";
3020 return " ORDER BY p.rating ASC, t.name ASC";
3023 return " ORDER BY p.rating DESC, t.name ASC";
3027 } // get_sort_order()
3030 * return the next to be shown slide show image
3032 * this function returns the URL of the next image
3033 * in the slideshow sequence.
3036 public function getNextSlideShowImage()
3038 $all_photos = $this->getPhotoSelection();
3040 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3041 $_SESSION['slideshow_img'] = 0;
3043 $_SESSION['slideshow_img']++;
3045 if($this->is_user_friendly_url()) {
3046 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3049 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3051 } // getNextSlideShowImage()
3054 * return the previous to be shown slide show image
3056 * this function returns the URL of the previous image
3057 * in the slideshow sequence.
3060 public function getPrevSlideShowImage()
3062 $all_photos = $this->getPhotoSelection();
3064 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3065 $_SESSION['slideshow_img'] = 0;
3067 $_SESSION['slideshow_img']--;
3069 if($this->is_user_friendly_url()) {
3070 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3073 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3075 } // getPrevSlideShowImage()
3077 public function resetSlideShow()
3079 if(isset($_SESSION['slideshow_img']))
3080 unset($_SESSION['slideshow_img']);
3082 } // resetSlideShow()
3087 * this function will get all photos from the fspot
3088 * database and randomly return ONE entry
3090 * saddly there is yet no sqlite3 function which returns
3091 * the bulk result in array, so we have to fill up our
3095 public function get_random_photo()
3104 /* if show_tags is set, only return details for photos which
3105 are specified to be shown
3107 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3109 INNER JOIN photo_tags pt
3114 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3117 $result = $this->db->db_query($query_str);
3119 while($row = $this->db->db_fetch_object($result)) {
3120 array_push($all, $row['id']);
3123 return $all[array_rand($all)];
3125 } // get_random_photo()
3128 * get random photo tag photo
3130 * this function will get all photos tagged with the requested
3131 * tag from the fspot database and randomly return ONE entry
3133 * saddly there is yet no sqlite3 function which returns
3134 * the bulk result in array, so we have to fill up our
3138 public function get_random_tag_photo($tagidx)
3142 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3145 DISTINCT pt1.photo_id as id
3148 INNER JOIN photo_tags
3149 pt2 ON pt1.photo_id=pt2.photo_id
3155 pt1.tag_id LIKE '". $tagidx ."'
3157 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3159 t1.sort_priority ASC";
3167 INNER JOIN photo_tags pt
3170 pt.tag_id LIKE '". $tagidx ."'";
3173 $result = $this->db->db_query($query_str);
3175 while($row = $this->db->db_fetch_object($result)) {
3176 array_push($all, $row['id']);
3179 return $all[array_rand($all)];
3181 } // get_random_tag_photo()
3184 * validates provided date
3186 * this function validates if the provided date
3187 * contains a valid date and will return true
3189 * @param string $date_str
3192 public function isValidDate($date_str)
3194 $timestamp = strtotime($date_str);
3196 if(is_numeric($timestamp))
3204 * timestamp to string conversion
3205 * @param integer $timestamp
3208 private function ts2str($timestamp)
3210 if(!empty($timestamp) && is_numeric($timestamp))
3211 return strftime("%Y-%m-%d", $timestamp);
3216 * extract tag-names from $_GET['tags']
3217 * @param string $tags_str
3220 private function extractTags($tags_str)
3222 $not_validated = split(',', $tags_str);
3223 $validated = array();
3225 foreach($not_validated as $tag) {
3226 if(is_numeric($tag))
3227 array_push($validated, $tag);
3235 * returns the full path to a thumbnail
3236 * @param integer $width
3237 * @param integer $photo
3240 public function get_thumb_path($width, $photo_idx, $version_idx)
3242 $md5 = $this->getMD5($photo_idx, $version_idx);
3243 $sub_path = substr($md5, 0, 2);
3244 return $this->cfg->thumb_path
3252 } // get_thumb_path()
3255 * returns server's virtual host name
3258 private function get_server_name()
3260 return $_SERVER['SERVER_NAME'];
3261 } // get_server_name()
3264 * returns type of webprotocol which is currently used
3267 private function get_web_protocol()
3269 if(!isset($_SERVER['HTTPS']))
3273 } // get_web_protocol()
3276 * return url to this phpfspot installation
3279 private function get_phpfspot_url()
3281 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3283 } // get_phpfspot_url()
3286 * returns the number of photos which are tagged with $tag_id
3287 * @param integer $tag_id
3290 public function get_num_photos($tag_id)
3292 if($result = $this->db->db_fetchSingleRow("
3293 SELECT count(*) as number
3296 tag_id LIKE '". $tag_id ."'")) {
3298 return $result['number'];
3304 } // get_num_photos()
3307 * check file exists and is readable
3309 * returns true, if everything is ok, otherwise false
3310 * if $silent is not set, this function will output and
3312 * @param string $file
3313 * @param boolean $silent
3316 private function check_readable($file, $silent = null)
3318 if(!file_exists($file)) {
3320 print "File \"". $file ."\" does not exist.\n";
3324 if(!is_readable($file)) {
3326 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3332 } // check_readable()
3335 * check if all needed indices are present
3337 * this function checks, if some needed indices are already
3338 * present, or if not, create them on the fly. they are
3339 * necessary to speed up some queries like that one look for
3340 * all tags, when show_tags is specified in the configuration.
3342 private function checkDbIndices()
3344 $result = $this->db->db_exec("
3345 CREATE INDEX IF NOT EXISTS
3352 } // checkDbIndices()
3355 * retrive F-Spot database version
3357 * this function will return the F-Spot database version number
3358 * It is stored within the sqlite3 database in the table meta
3359 * @return string|null
3361 public function getFspotDBVersion()
3363 if($result = $this->db->db_fetchSingleRow("
3364 SELECT data as version
3367 name LIKE 'F-Spot Database Version'
3369 return $result['version'];
3373 } // getFspotDBVersion()
3376 * parse the provided URI and will returned the requested chunk
3377 * @param string $uri
3378 * @param string $mode
3381 public function parse_uri($uri, $mode)
3383 if(($components = parse_url($uri)) !== false) {
3387 return basename($components['path']);
3390 return dirname($components['path']);
3393 return $components['path'];
3403 * validate config options
3405 * this function checks if all necessary configuration options are
3406 * specified and set.
3409 private function check_config_options()
3411 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3412 $this->_error("Please set \$page_title in phpfspot_cfg");
3414 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3415 $this->_error("Please set \$base_path in phpfspot_cfg");
3417 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3418 $this->_error("Please set \$web_path in phpfspot_cfg");
3420 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3421 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3423 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3424 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3426 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3427 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3429 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3430 $this->_error("Please set \$db_access in phpfspot_cfg");
3432 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3433 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3435 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3436 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3438 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3439 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3441 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3442 $this->_error("Please set \$photo_width in phpfspot_cfg");
3444 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3445 $this->_error("Please set \$mini_width in phpfspot_cfg");
3447 if(!isset($this->cfg->thumbs_per_page))
3448 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3450 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3451 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3453 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3454 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3456 if(!isset($this->cfg->hide_tags))
3457 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3459 if(!isset($this->cfg->theme_name))
3460 $this->_error("Please set \$theme_name in phpfspot_cfg");
3462 if(!isset($this->cfg->logging))
3463 $this->_error("Please set \$logging in phpfspot_cfg");
3465 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3467 if(!isset($this->cfg->log_file))
3468 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3470 if(!is_writeable($this->cfg->log_file))
3471 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3475 /* remove trailing slash, if set */
3476 if($this->cfg->web_path == "/")
3477 $this->cfg->web_path = "";
3478 elseif(preg_match('/\/$/', $this->cfg->web_path))
3479 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3481 return $this->runtime_error;
3483 } // check_config_options()
3486 * cleanup phpfspot own database
3488 * When photos are getting delete from F-Spot, there will remain
3489 * remain some residues in phpfspot own database. This function
3490 * will try to wipe them out.
3492 public function cleanup_phpfspot_db()
3494 $to_delete = Array();
3496 $result = $this->cfg_db->db_query("
3499 ORDER BY img_idx ASC
3502 while($row = $this->cfg_db->db_fetch_object($result)) {
3503 if(!$this->db->db_fetchSingleRow("
3506 WHERE id='". $row['img_idx'] ."'")) {
3508 array_push($to_delete, $row['img_idx'], ',');
3512 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3514 $this->cfg_db->db_exec("
3516 WHERE img_idx IN (". implode($to_delete) .")
3519 } // cleanup_phpfspot_db()
3522 * return first image of the page, the $current photo
3525 * this function is used to find out the first photo of the
3526 * current page, in which the $current photo lies. this is
3527 * used to display the correct photo, when calling showPhotoIndex()
3529 * @param integer $current
3530 * @param integer $max
3533 private function getCurrentPage($current, $max)
3535 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3536 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3537 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3543 } // getCurrentPage()
3548 * this function tries to find out the correct mime-type
3549 * for the provided file.
3550 * @param string $file
3553 public function get_mime_info($file)
3555 $details = getimagesize($file);
3557 /* if getimagesize() returns empty, try at least to find out the
3560 if(empty($details) && function_exists('mime_content_type')) {
3562 // mime_content_type is marked as deprecated in the documentation,
3563 // but is it really necessary to force users to install a PECL
3565 $details['mime'] = mime_content_type($file);
3568 return $details['mime'];
3570 } // get_mime_info()
3573 * return tag-name by tag-idx
3575 * this function returns the tag-name for the requested
3576 * tag specified by tag-idx.
3577 * @param integer $idx
3580 public function get_tag_name($idx)
3582 if($result = $this->db->db_fetchSingleRow("
3586 id LIKE '". $idx ."'")) {
3588 return $result['name'];
3597 * parse user friendly url which got rewritten by the websever
3598 * @param string $request_uri
3601 private function parse_user_friendly_url($request_uri)
3603 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3605 $options = explode('/', $request_uri);
3607 switch($options[1]) {
3609 if(is_numeric($options[2])) {
3610 $this->session_cleanup();
3611 //unset($_SESSION['start_action']);
3612 //unset($_SESSION['selected_tags']);
3613 $_GET['mode'] = 'showp';
3614 return $this->showPhoto($options[2]);
3618 if(is_numeric($options[2])) {
3621 if(isset($options[3]) && is_numeric($options[3]))
3622 $width = $options[3];
3623 if(isset($options[4]) && is_numeric($options[4]))
3624 $version = $options[4];
3625 require_once "phpfspot_img.php";
3626 $img = new PHPFSPOT_IMG;
3627 $img->showImg($options[2], $width, $version);
3632 if(is_numeric($options[2])) {
3633 $this->session_cleanup();
3634 $_GET['tags'] = $options[2];
3635 $_SESSION['selected_tags'] = Array($options[2]);
3636 if(isset($options[3]) && is_numeric($options[3]))
3637 $_SESSION['begin_with'] = $options[3];
3638 return $this->showPhotoIndex();
3644 } // parse_user_friendly_url()
3647 * check if user-friendly-urls are enabled
3649 * this function will return true, if the config option
3650 * $user_friendly_url has been set. Otherwise false.
3653 private function is_user_friendly_url()
3655 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3660 } // is_user_friendly_url()
3665 * this function will cleanup user's session information
3667 private function session_cleanup()
3669 unset($_SESSION['begin_with']);
3670 $this->resetDateSearch();
3671 $this->resetPhotoView();
3672 $this->resetTagSearch();
3673 $this->resetNameSearch();
3674 $this->resetDateSearch();
3677 } // session_cleanup()
3680 * get database version
3682 * this function queries the meta table
3683 * and returns the current database version.
3687 public function get_db_version()
3689 if($row = $this->cfg_db->db_fetchSingleRow("
3694 meta_key LIKE 'phpfspot Database Version'
3697 return $row['meta_value'];
3703 } // get_db_version()
3706 * get photo versions
3708 * this function returns an array of all available
3709 * alterntaive versions of the provided photo id.
3710 * has alternative photo versions available
3715 public function get_photo_versions($idx)
3717 $versions = Array();
3719 $result = $this->db->db_query("
3725 photo_id LIKE '". $idx ."'");
3727 while($row = $this->cfg_db->db_fetch_object($result)) {
3728 array_push($versions, $row['version_id']);
3733 } // get_photo_versions()
3736 * check for invalid version of photo
3738 * this function validates the provided photo-id and version-id
3740 * @param int $photo_idx
3741 * @param int $version_idx
3744 public function is_valid_version($photo_idx, $version_idx)
3746 /* the original version is always valid */
3747 if($version_idx == 0)
3750 if($versions = $this->get_photo_versions($photo_idx)) {
3751 if(in_array($version_idx, $versions))
3757 } // is_valid_version()
3760 * get photo version name
3762 * this function returns the name of the version
3763 * identified by the photo-id and version-id.
3765 * @param int $photo_idx
3766 * @param int $version_idx
3769 public function get_photo_version_name($photo_idx, $version_idx)
3771 if($row = $this->db->db_fetchSingleRow("
3777 photo_id LIKE '". $photo_idx ."'
3779 version_id LIKE '". $version_idx ."'")) {
3781 return $row['name'];
3787 } // get_photo_version_name()
3791 public function is_valid_width($image_width)
3793 if(in_array($image_width,
3794 Array($this->cfg->thumb_width,
3795 $this->cfg->photo_width,
3796 $this->cfg->mini_width,
3803 } // is_valid_width()