<?php
/**************************************************************************************************
| Ionicware
| http://www.ionicware.com
| support@ionicware.com
|
|**************************************************************************************************
|
| Copyright (c) 2011 ionicware.com. All rights reserved.
|**************************************************************************************************/

include("include/config.php");
include("include/functions/import.php");
include('include/functions/includes/service.php');
include('ajax/filterMap.php');

/**
 * Redirect the user to the old version if not using the
 * new theme.
 */
if ($config['themeUni']	!= 1)
{
	include('archive/view.php');
	die();
}

$pid 	= is_array($_REQUEST['id']) ? 0 : intval(strip_tags($_REQUEST['id'])); // ?x[]=: as on 7.4, not a TypeError (#1925)
$alert 	= intval(cleanit($_REQUEST['alert']));

/*
 * Get the POST token
 * 1/6/2013
 */
$getToken = $_REQUEST['token'];

if ($pid == "") redirectAndDie('/');

/**
 * If we have a notification lets check it
 * 5/8/2020
 */
if (intval($alert) > 0 && intval($__loggedUserDetails['USERID']) > 0)
{
	checkAlertWithId($alert, $__loggedUserDetails['USERID']);
	checkUpdateAlerts($__loggedUserDetails['USERID'], 'service', $OID, 'bookmark');
}

/**
 * In order to eliminate duplicate quries to the same table, we issue one call here to get all
 * details of the service, including the type.
 *
 * Since this is a service only file, we default that as hard-coded
 * 4/15/2019
 */
$service = removeArrayIntIndices(getAllServiceDetails($pid, 'service'));

if ($service['PID'] > 0
	&& ($service['active'] == 0 ||
		$service['active'] == 1 ||
		$service['active'] == 2 ||
		$service['active'] == 4)
	)
{
	$categoryDetails 	= GetAllCategoryDetailsFromType($service['type'], $service['category']);
	$userData			= GetAllUserDetails($service['USERID']);
	$profileData 		= $userData['profile_data'];

	if (!empty($profileData))
	{
		$profileDataArray 		= json_decode($profileData, true);
		$workTo		 			= $profileDataArray['workTo'];
		$outOffice 				= $profileDataArray['outOffice'];
		$disableOffice 			= $profileDataArray['disable_office'];
		$workFrom		 		= $profileDataArray['workFrom'];
		$allow_custom			= $profileDataArray['allow_custom'];
	}

	/**
	 * Combine our three array values
	 */
	$p[0] = array_merge($service, $categoryDetails, $userData);
}

/*
 * A deleted/rejected service (active=3) or an ID that doesn't exist never fills
 * $p[0], and used to fall through to the slug redirect below, which sent guests
 * a 301 to "//{id}/" (a 404). Search Console had tens of thousands of those.
 * Answer 410 Gone for a removed service and 404 for anything else instead.
 * 2026-09-23
 */
if (empty($p[0]['PID']))
{
	http_response_code(($service['PID'] > 0 && $service['active'] == 3) ? 410 : 404);
	include '404.php';
	die();
}

/**
 * I'm not sure what happened to this function but it prevented
 * the hack that downloads image locally using getimagesize() which
 * causes the file to be downloaded.
 *
 * 2021-07-16 09:52:27 JD
 *
 * This was causing the site to be REALLY slow
 *
 * 2021-08-16 10:16:04 JD
 */
$p[0]['gdesc'] = removeInvalidImages($p[0]['gdesc']);

/*
* Added 12/30/2011
* for deleted and suspended gigs
*/
if ($p[0]['active'] != 1 && $p[0]['active'] != 2 && $p[0]['active'] != 3 && $p[0]['active'] != 4 && $p[0]['active'] != 0) redirectAndDie('/');

/**
 * If the status is 0 or 4, only the owner should be
 * able to view.
 *
 * 4 == Draft
 * 0 == Inactive
 *
 */
if (($p[0]['active'] == 4 || $p[0]['active'] == 0) && $p[0]['USERID'] != $__loggedUserDetails['USERID'])
{
	// Not public yet: a real 404 rather than a redirect to the homepage (a soft 404 to Google).
	http_response_code(404);
	include '404.php';
	die();
}

/**
 * Find the category and child
 */
if ($service['type'] == 7)
{
	$pCatUrl 	= 'softwarecats';
	$catTag 	= 'softwaretags';
}
else if ($service['type'] == 4)
{
	$pCatUrl 	= 'twittercats';
	$catTag 	= 'twittertags';
}
else
{
	$pCatUrl 	= 'categories';
	$catTag 	= 'tags';
}

/*
* Get all of the parents
* Category data for the nav
* 4/12/2019
*/
if ($p[0]['parentid'] > 0)
{
	$parentCatDetails = GetAllCategoryDetailsFromType($service['type'], $p[0]['parentid']); // Art & design

	if ($parentCatDetails['parentid'] > 0)
	{
		$pParentCatDetails 	= GetAllCategoryDetailsFromType($service['type'], $parentCatDetails['parentid']);

		if ($pParentCatDetails['parentid'] > 0)
		{
			$ppParentCatDetails 	= GetAllCategoryDetailsFromType($service['type'], $pParentCatDetails['parentid']);
			$preCategory[] 			= $ppParentCatDetails;
		}

		$preCategory[] 		= $pParentCatDetails;
	}

	$preCategory[] 		= $parentCatDetails;

	$categoryInclude = $preCategory[0]['seo'];
}
else
	$categoryInclude = $p[0]['seo'];

if ($type == 7 || $service['type'] == 7) $categoryInclude = 'software';

/*
* If the user is viewing the page
* from an ID only, lets move them
* to the SEO page.
* 11/26/2011
*/
if (empty($title) && stristr($_SERVER['REQUEST_URI'], '/view.php?id='))
{
	$title = seo_clean_titles($p[0]['gtitle']);
	redirectAndDie("/{$p[0]['seo']}/{$pid}/{$title}");
}

/*
* Added on 12/8/2011
* in case something gets changed it will
* forward the user to the correct URL and
* preserve the SEO
*
* The affiliate redirect below must still win over this one, so only stop here
* when there is no affiliate id. The page used to render in full under the 301.
*/
$affLinkId = affiliateLinkId($_REQUEST['aff'] ?? '');
if (($_REQUEST['gig'] != seo_clean_titles($p[0]['gtitle'])
			|| $_REQUEST['title'] != seo_clean_titles($p[0]['seo']))
			&& !stristr($_SERVER['HTTP_HOST'], 'a.')
	)
{
	$title = seo_clean_titles($p[0]['gtitle']);
	header("HTTP/1.1 301 Moved Permanently");
	header("location: $config[baseurl]/{$p[0]['seo']}/{$pid}/{$title}");
	if ($affLinkId <= 0) die();
}

/*
* Added on 4/6/2012
* If the user is linking into a gig using an
* affiliate link, forward them to the correct
* URL
*
* Aff should go to the affiliate
*/
if ($affLinkId > 0 && !stristr($_SERVER['HTTP_HOST'], 'a.'))
{
	$title = seo_clean_titles($p[0]['gtitle']);

	/*
	* 4/1/2013
	* If there is a coupon,
	* forward that on.
	*/
	if (!empty($_REQUEST['coupon']))
	{
		header("location: $config[aurl]/linkin/{$affLinkId}/{$p[0]['seo']}/{$pid}/{$title}?coupon={$_REQUEST['coupon']}");
		die();
	}
	else
	{
		header("HTTP/1.1 301 Moved Permanently");
		header("location: $config[aurl]/linkin/{$affLinkId}/{$p[0]['seo']}/{$pid}/{$title}");
		die();
	}
}

/*
* If we have extra queries on the end
* of our views, make those go away as
* well. This is form of negative SEO.
* 7/9/2012
* Bad Example: title=buy-facebook-likes-fans&id=7971&gig=Get-You-50-Mannual-Facebook-Likes&crapmode=1
*/
$queryArray  = explode('&', $_SERVER['QUERY_STRING']);
if (count($queryArray) > 3 && ($_REQUEST['lang'] == 'en' || empty($_REQUEST['lang'])) && empty($_REQUEST['coupon'])
	&& empty($_REQUEST['uni']) && empty($_REQUEST['aff']))
{
	/*
	* Someone has negatived SEO'd us. :(
	*/
	$title = seo_clean_titles($p[0]['gtitle']);
	header("HTTP/1.1 301 Moved Permanently");
	header("location: $config[baseurl]/{$p[0]['seo']}/{$pid}/{$title}");
	die();
}

/*
* If the site is MBL, adjust the
* price up by 200%
* 4/6/2014
*/
if (stristr($_SERVER["HTTP_HOST"], 'seocheckout.com'))
{
	$p[0]['price'] 			= AdjustPriceUpSOC($p[0]['price']);
	$p[0]['onsaleamount'] 	= AdjustPriceUpSOC($p[0]['onsaleamount']);
	$p[0]['extraprice1'] 	= AdjustPriceUpSOC($p[0]['extraprice1']);
	$p[0]['extraprice2'] 	= AdjustPriceUpSOC($p[0]['extraprice2']);
	$p[0]['extraprice3'] 	= AdjustPriceUpSOC($p[0]['extraprice3']);
}
else  if (stristr($_SERVER["HTTP_HOST"], 'seolads.com'))
{
	$p[0]['price'] 			= AdjustPriceUpSOC($p[0]['price']);
	$p[0]['onsaleamount'] 	= AdjustPriceUpSOC($p[0]['onsaleamount']);
	$p[0]['extraprice1'] 	= AdjustPriceUpSOC($p[0]['extraprice1']);
	$p[0]['extraprice2'] 	= AdjustPriceUpSOC($p[0]['extraprice2']);
	$p[0]['extraprice3'] 	= AdjustPriceUpSOC($p[0]['extraprice3']);
}
else if (stristr($_SERVER["HTTP_HOST"], 'microjunky.com'))
{
	$p[0]['price'] 			= AdjustPriceUpMj($p[0]['price']);
	$p[0]['onsaleamount'] 	= AdjustPriceUpMj($p[0]['onsaleamount']);
	$p[0]['extraprice1'] 	= AdjustPriceUpMj($p[0]['extraprice1']);
	$p[0]['extraprice2'] 	= AdjustPriceUpMj($p[0]['extraprice2']);
	$p[0]['extraprice3'] 	= AdjustPriceUpMj($p[0]['extraprice3']);
}

/*
* We need the english title for
* the seo URLs.
* 9/10/2012
*/
$p[0]['gtitleEng'] = $p[0]['gtitle'];

/*
* Json encoded extras
* The first item will be fast delivery
* 5/24/2016
*
* Structure
* Array (
* 		[0] => Array (
*				'quick' => 1, // Enabled = 1, 0 = disabled
*				'days'	=> int,
*				'price' => int
*			   ),
*		[1] => Array (
*	 			'extra' => 'description',
*				'price' => int,
*				'days'	=> int
*			   ),
*		[2] => Array (
*	 			'extra' => 'description',
*				'price' => int,
*				'days'	=> int
*			   ),
*     REPEAT
* );
*/
if (!empty($p[0]['extras_data']))
{
	$extrasData 	= json_decode($p[0]['extras_data'], true);

	/*
	* Enable string On addon1
	* addon[1]=1&addon[2]=1&addon[3]=0
	*
	* Disable string on addon1
	* addon[1]=0&addon[2]=1&addon[3]=0
	*/
	foreach ($extrasData as $key => &$addon)
	{
		if (!empty($addon['name']))
		{
			if (stristr($_SERVER["HTTP_HOST"], 'seocheckout'))
			{
					$addon['price'] 	= AdjustPriceUpSOC($addon['price']);
			}
			else if (stristr($_SERVER["HTTP_HOST"], 'seolads'))
			{
				$addon['price'] 	= AdjustPriceUpSOC($addon['price']);
			}
		}
	}
}

$uniThemeExtras = $extrasData;

/*
* This is for coupon data
* 10/2/2016
*/
if ($p[0]['USERID'] == $__loggedUserDetails['USERID'] || isStaff()) $couponsA = json_decode($p[0]['coupons_data'], true);

/*
* Added 10/8/2011 to this POS code
* to enable line breaks in text.
*
* Updated on 10/15/2011 for bbcode
*/
$p[0]['desc_nobbcode'] 	= RemoveTextBbcode($p[0]['gdesc']);
$p[0]['gdesc'] 			= ConditionText($p[0]['gdesc']);

/*
* Do some extra work for the UNI theme.
* 06/27/2018
*/
$p[0]['userlevel'] = getUserLevelFromLevel($p[0]['userlevel']);

$title 	= stripslashes($p[0]['gtitle']);
$uname 	= stripslashes($p[0]['username']);
$PD 	= stripslashes($p[0]['PID']);
$uid 	= stripslashes($p[0]['USERID']);

/*
* Get rid of crap in the title for
* the listing display
* 9/11/2013
*/
if (empty($config['lang']))
	$title = FormatListingTitle($title);

/*
* Format those things
* 6/30/2012
*/
$p[0]['gtags'] 	= FormatTags($p[0]['gtags']);
$tags 			= explode(" ", $p[0]['gtags']);

/*
* Is the User online?
* 2/1/2019
*/
(IsUserOnline($uid, $p[0]['lastlogin'])) ? $p[0]['online'] = 1 : $p[0]['online'] = 0;

/*
* Get the users total orders for this
* 8/5/2012
*/
$p[0]['usertotalorders'] = GetBuyerTotalOrders($p[0]['PID']);

/**
 * Make sure there's at least 2 orders to check if
 * there's a trend on extras
 */
// if (count($uniThemeExtras) > 0 && $p[0]['usertotalorders'] > 2)
// {
// 	$p[0]['usertotalorders'] = getPopularExtras($p[0]['PID']);
// }

/*
* If we have additional data,
* unserialize it and provide it to the
* view.
* 10/28/2011
*/
$additionalData = unserialize($p[0]['additional_data']);

/**
 * If this order was an upgrade they shouldn't be here
 * 2022-06-10 11:51:23 - SE
 */
if ($additionalData['is_upgrade'] == 1) redirectAndDie('/');

/*
* If type is 7 (software), we have to
* prepare additional data to present
* 12/3/2012
*/
if ($type == 7 || $service['type'] == 7)
{ // Software
	/**
	 * The unitheme allows free input on platforms
	 * 2/26/2019
	 */
	if (is_array($additionalData['platforms']))
		$additionalData['platforms'] 			= implode(', ', $additionalData['platforms']);

	$additionalData['version']				= $additionalData['version'];
	$additionalData['requirements'] 		= ConditionText($additionalData['requirements']);
	$additionalData['license']				= $additionalData['license'];
	$additionalData['instant_license']		= $additionalData['instant_license'];
	$additionalData['documentation']		= $additionalData['documentation'];
	$additionalData['demo_url']				= $additionalData['demo_url'];
	$additionalData['browsers_supported']	= $additionalData['browsers_supported'];

}

/*
* The SEO URL
*/
$seoUrl = $config['baseurl'] . '/' . $p[0]['seo'] . '/' . $PD . '/' . seo_clean_titles($p[0]['gtitleEng']);

/*
 * Canonical is the listing's real URL, not whatever URL was requested (which is
 * what config.php builds). Translated pages keep their own /{lang} URL.
 * 2026-09-23
 */
if (empty($_REQUEST['lang']) || $_REQUEST['lang'] == 'en')
{
	$config['canonicalUrl'] = str_ireplace('seoclerks.com', 'seoclerk.com', $seoUrl);
}

/*
* Affiliate URL for promotion
* 10/28/2012
*/
if ($__loggedUserDetails['USERID'] > 0 && $__loggedUserDetails['USERID'] != $p[0]['USERID'])
{
	$affUrl = $config['aurl'] . '/linkin/' . $__loggedUserDetails['USERID'] . '/' . $p[0]['seo'] . '/' . $PD . '/' . seo_clean_titles($p[0]['gtitleEng']);
}
else
{
	$affUrl = $config['baseurl'] . '/' . $p[0]['seo'] . '/' . $PD . '/' . seo_clean_titles($p[0]['gtitleEng']);
}

/*
* If the user has removed the onsale
* then do not display that.
* 1/21/2012
*/
if ($p[0]['onsale'] == 0) $p[0]['onsaleamount'] = 0;

/*
* Get the Service Header Title
* 1/21/2012
*/
if (empty($config['lang']))
{
	$gigHeaderTitle = GetTypeDisplayTitle($type, $title, $p[0]['price'], true, ' ', $p[0]['onsaleamount']);
	$gigPageTitle 	= GetTypeDisplayTitle($type, $title, $p[0]['price'], true, '');
}
else
{
	$gigHeaderTitle = $title;
	$gigPageTitle 	= $title;
}

/*
* Add the username to the
* page title.
* 4/8/2013
*/
$newPageTitle = GetTypeDisplayTitle($type, $title, $p[0]['price'], true, ' ');
$gigPageTitle = trim($newPageTitle); //$uname . ' ' . trim($gigPageTitle); //$gigPageTitle; // $uname . ' ' .

$noForDisplayTitle = str_ireplace(' for $' . $p[0]['price'], '', $gigHeaderTitle);
$noForDisplayTitle = str_ireplace(' for $' . $p[0]['onsaleamount'], '', $noForDisplayTitle);

/*
* On Sale days left caculation.
* 1/10/2013
*/
if ($p[0]['onsale'] == 1)
{
	$days = $p[0]['onsaleexpire'] - time();

	/*
	* Don't divide if 0
	* this causes the -123123123 days on sale problem
	* 12/17/2013
	*/
	if ($days > 0)
	{
		$p[0]['onsaledaysleft']  = intval($days / 86400);
	}
	else
	{
		$p[0]['onsaledaysleft'] = 0;
	}
}

/*
* Add to their recently viewed
* items
* 10/21/2019
*/
if (!empty($_SESSION['recentlyviewed']))
{
	$recentArray 	= json_decode($_SESSION['recentlyviewed'], true);

	if(empty($recentArray))
	{
		$recentArray[0] = $pid;
	}

	$key = array_search($pid, $recentArray);

	if (!empty($key))
	{
		unset($recentArray[$key]);
	}

	/**
	 * Add the pid to the beginning of the array
	 */
	array_unshift($recentArray, $pid);

	/**
	 * Remove the last value of the array if it gets past a value
	 */
	if (count((array) $recentArray) > 8)
	{
		array_pop($recentArray);
	}

	$_SESSION['recentlyviewed'] = json_encode($recentArray);
}
else
{
	$_SESSION['recentlyviewed'] = json_encode(array($pid));
}

/*
* Article
*/
if ($type == 1)
{
	$config['pagetitle']	= $title."  ".$lang[504].$p[0]['price'];
}
else if ($type == 4)
{
	$config['pagetitle']	= $lang[536] ."  " . $title ."  ". $lang[537] . " " . $p[0]['price'];
}
else if ($type == 7)
{ // Software
	$config['pagetitle']	= $gigPageTitle;
}
else if ($type == 8)
{ // Theme
	$config['pagetitle']	= $gigPageTitle;
}
else
{
	$config['pagetitle']	= $gigPageTitle;
}

/*
* Regardless of what type, if there is
* meta details set those.
* Added 11/9/2011
*
* 4/24/2019 - converted to use the variable $service
*/
if (!empty($service['metatitle']))
{
	$config['pagetitle']	= $service['metatitle'];
}
if (!empty($service['metakeywords']))
{
	$config['metakeywords']	= $service['metakeywords'];
}
else
{
	$config['metakeywords'] = str_ireplace('"', '', substr(strip_tags($service['gtags']), 0));

	/**
	 * HACK Add spacing between keywords for old and new theme
	 */
	$config['metakeywords'] = str_ireplace(',', ', ', str_ireplace(' ', ',', $config['metakeywords']));
}
if (!empty($service['metadescription']))
{
	$config['metadescription']	= $service['metadescription'];
}
else
{
	$config['metadescription'] = trim(str_ireplace('"', '', substr(strip_tags(RemoveBbCode($service['gdesc'])), 0, 170)));
	$config['metadescription'] = preg_replace('/\s+/', ' ', $config['metadescription']);
}

/*
* added 3/28/2012
*/
$p[0] = BuildRatingContent($p[0]);

/**
 * Issue with no ratings on google WMT, don't
 * show the ratings at all if none are found.
 * 3/16/2021 - SE
 */
if ($p[0]['combined_rating'] > 0)
{
	$p[0]['gigHeaderTitle'] = $gigHeaderTitle;
	$p[0] 					= BuildFooterRatingContent($p[0]);
}

/*
* Let's add some content to the footerRating
* 9/5/2016
*/
if ($p[0]['active'] == 1)
{
	$p[0]['footerRatingContent'] .= ' <div class="smalllighter inlinechange" itemprop="offers" itemscope itemtype="http://schema.org/Offer"><span itemprop="priceCurrency" content="USD">$</span><span itemprop="price" content="' . $p[0]['price'] . '">' . $p[0]['price'] .'</span>  - <link itemprop="availability" href="http://schema.org/InStock" />In stock</div>';
}

/*
* Get the average response time of
* the seller
* 8/7/2013
*/
$p[0]['average_response_time'] = GetResponseTime($p[0]['average_response_time']);

/*
* Get the average comletion time
* 8/8/2013
*/
$p[0]['average_completion_time'] = GetResponseTime($p[0]['average_completion_time'], true);

/*
* Get the total amount of ratings
* which will be used by the AJAX
* more button
* 4/28/2013
*/
$query 			= "SELECT COUNT(A.RID) as total,
						SUM(bad) as badTotal,
						SUM(good) as goodTotal
					FROM
						ratings A,
						seoclerks.members B
					WHERE A.PID='".addslashes($PD)."'
					AND A.RATER=B.USERID";

$executequery 	= $conn->CacheExecute(220, $query);
$total_ratings 	= $executequery->fields['total']+0;
$totalRatings 	= $total_ratings;
$goodRatings	= $executequery->fields['goodTotal'];
$badRatings		= $executequery->fields['badTotal'];

/**
 * rating status array values for rating status component
 */
$ratingStatus['percent'] 		 		= ((legacyDivide($goodRatings, $totalRatings)) * 100); // no ratings: NAN as on 7.4
$ratingStatus['percent'] 		 		= number_format($ratingStatus['percent'], 1);
$ratingStatus['positiveRatings'] 		= $goodRatings;
$ratingStatus['negitiveRatings'] 		= $badRatings;
$ratingStatus['numberOfRatings'] 		= $totalRatings;
$ratingStatus['positiveRatingsPercent'] = legacyDivide($goodRatings, $totalRatings) * 100;
$ratingStatus['negitiveRatingsPercent'] = legacyDivide($badRatings, $totalRatings) * 100;
$ratingStatus['ratingPercent'] 			= (float) $ratingStatus['percent'] / 20; // "nan" (no ratings): 7.4 read 0, 8 throws
$ratingStatus['ratingPercent']			= number_format($ratingStatus['ratingPercent'], 1);
$totalRatingPercent						= $ratingStatus['percent'];

if ($totalRatings > 0)
{
	/*
	* Get the orders that are passed due
	* 8/14/2013
	*/
	$query 			= "SELECT
							*
						FROM
							portfolio
						WHERE PID='".addslashes($PD)."'
						ORDER BY id ASC
						LIMIT 10
						";

	$executequery 		= $conn->CacheExecute(180, $query);
	$portfolioReviews 	= $executequery->getrows();
}

/*
* Get the orders that are passed due
* 8/14/2013
*/
$query 			= "SELECT
						OID,
						stime,
						extradays1,
						extradays2,
						extradays3,
						extra1ordered,
						extra2ordered,
						extra3ordered,
						days,
						status,
						quantity,
						extras_data
					FROM
						orders
					WHERE PID='".addslashes($PD)."'
					AND status in (1, 4, 6)";
$executequery 	= $conn->CacheExecute(180, $query);
$order_rows		= $executequery->getrows();

$total_late = 0;
foreach ($order_rows as $order)
{
	if ($order['status'] != 4)  // don't count delivered for late
	{
		$now 		= time();
		$lateTime 	= $order['days'];

		if ($order['extra1ordered'] == 1) $lateTime = $lateTime + $order['extradays1'];
		if ($order['extra2ordered'] == 1) $lateTime = $lateTime + $order['extradays2'];
		if ($order['extra3ordered'] == 1) $lateTime = $lateTime + $order['extradays3'];


		$extrasData 	= json_decode($order['extras_data'], true);

		/*
		* If this is a quick delivery, the days are
		* set in stone
		* 6/10/2016
		*/
		if ($extrasData[0]['ordered'] == 1)
		{
			$lateTime = $extrasData[0]['days'];
		}
		else
		{
			foreach ($extrasData as $key => &$addon)
			{
				if ($addon['ordered'] == 1 && $key > 0)
				{
					$lateTime = $lateTime + legacyNumber($addon['days']); // an ordered extra can carry "days":""
				}
			}
		}

		/*
		* Add the quantity
		* 9/9/2013
		*/
		$lateTime = $lateTime * $order['quantity'];
		$lateTime = ($lateTime * 86400) + legacyNumber($order['stime']); // orders.stime is '' on some rows

		/*
		* Is it late?
		*/
		if ($now > $lateTime) $total_late++;
	}
}

update_viewcount($pid);

/*
* Update the viewed content so workers
* only see it once. :)
* 11/9/2011
*/
if ($__loggedUserDetails['USERID'] == $p[0]['USERID'] && $p[0]['service_author_bookmarktip'] == 0 && $p[0]['active'] == 0)
{
	/**
	 * New services will have a pop up showing upgrade options
	 */
	$config['include']['modals'][6] = 'views/modal/view_modal-more.php';
	$config['include']['footerJs'] .= '<script type="text/javascript">(function() {$("#modalInfo").modal("show");})();</script>';

	/**
	 * If we have upgraded our service we need to show this message
	 * 2021-07-27 13:31:20 - SE
	 */
	if (!empty($additionalData['upgradesBought']) || !empty($p[0]['catfeatured_upgrade']) || !empty($p[0]['randfeatured_upgrade']) || !empty($p[0]['highlight_upgrade']))
	{
		$infoModalData['title'] 		= $lang[4020];
	}
	else
	{
		$infoModalData['title'] 		= $lang[793];
		$infoModalData['desc'] 			= $lang[3910];
		$infoModalData['buttonText'] 	= $lang[3908];
		$infoModalData['buttonLink'] 	= "order/7608";
		$infoModalData['buttonColor'] 	= "btn-primary";
	}

	$infoModalData['img'] 			= $config['baseurl'] . "/assets/img/illustrations/rocket.png";
	$infoModalData['cardOn'] 		= true;

	$query = "UPDATE posts
				SET author_bookmarktip = 1
				WHERE PID = ?";

	$preStat = array($PD);

	$conn->execute($query, $preStat);
}

/*
* Get the last login date in days for the gig
* 2/1/2012
*/
$p[0]['lastLoginDays'] = GetUserLastLoginDays($p[0]['USERID'], $p[0]['lastlogin']);

/*
* Get the time_added in
* days/minutes/years/etc ago
* 8/9/2013
*/
$p[0]['time_added_ago'] = GetTimeAgo($p[0]['time_added']);

/*
 * Assign the from variable
 * so conversations come from there
 * 12/28/2013
 */
$from = 'sc_' . $p[0]['PID'];

/*
* Generate a JWT token for the socket server.
*
* Prefaced with __ so as not to conflict elsewhere.
*
* 10/18/2018
*/
$__socketParams = array(
	"type" => "profile",
	"acc" => array(),
	"ouid" => [intval($userData['USERID'])]
);

/**
 * If our additional data is blank, add all of the p1/2/3 images to it.
 */
if (empty($additionalData) || !is_array($additionalData) || empty($additionalData['images']))
{
	$jsonImageArray[0] = array(
		'fileName' 		=> $p[0]['p1'],
		'relativePath' 	=> '/' . $p[0]['p1']
	);
	if (!empty($p[0]['p2']))
	{
		$jsonImageArray[1]['fileName'] 		= $p[0]['p2'];
		$jsonImageArray[1]['relativePath'] 	= '/' . $p[0]['p2'];
	}
	if (!empty($p[0]['p3']))
	{
		$jsonImageArray[2]['fileName'] 		= $p[0]['p3'];
		$jsonImageArray[2]['relativePath'] 	= '/' . $p[0]['p3'];
	}

	$additionalData['images'] = $jsonImageArray;
}
else
{
	$additionalData['images'] = json_decode($additionalData['images'], true);
}

if (!empty($portfolioReviews))
{
	foreach ($portfolioReviews as $portfolio)
	{
		$a['fid'] 					= $portfolio['images'];
		$fd 						= insert_file_details($a);

		if (strpos($fd[0]['fname'], '.png') !== false
		|| strpos($fd[0]['fname'], '.jpg') !== false
		|| strpos($fd[0]['fname'], '.jpeg') !== false
		|| strpos($fd[0]['fname'], '.gif') !== false
		|| strpos($fd[0]['fname'], '.mp4') !== false)
		{
			$imagesPort['relativePath'] = '/files/user/community/' . $fd[0]['s'] . '/' . $fd[0]['fname'];
			$imagesPort['mediaUrl']		= $config['communityurl'] . $fd[0]['s'] . '/' . $fd[0]['fname']; // full URL from the config key, so the CDN cutover reaches these too (issue #979)
			$imagesPort['title']		= $portfolio['message'];
			$imagesPort['reviewer']		= insert_get_username_from_userid(array('USERID' => $portfolio['buyer_USERID']));

			array_push($additionalData['images'], $imagesPort);
		}
	}
}

/**
 * Tiered pricing - issue #908
 *
 * Empty for a listing that sells on a single price, which is most of
 * them, and the comparison then renders nothing at all.
 *
 * 2026-08-26 JD
 */
$serviceTiers		= getServiceTiers($additionalData);
$serviceTierKey		= resolveTierKey($serviceTiers, isset($_REQUEST['tier']) ? $_REQUEST['tier'] : 0);

$includesData 						= unserialize($p[0]['includes_data']);
$additionalData['fields'] 			= json_decode($additionalData['fields'], true);
$additionalData['includedFeature'] 	= json_decode($includesData, true);
$profileDataArray 					= json_decode($p[0]['profile_data'], true);
$p[0]['pic']						= $profileDataArray['pic'];

/**
 * Get our CSFR token
 */
$CSFR 			= GenerateCSFRCode();

/**
 * Include the share modal
 */
if ($__loggedUserDetails['USERID'] > 0 && $__loggedUserDetails['USERID'] != $p[0]['USERID'])
	// The listing's rate when it is above the site default, a setting (2026-10-20 JD)
	if ($p[0]['affiliatecomission'] > $config['standardAffiliateCommissionOurMethod'])
		$shareComponent['desc'] = str_replace($config['standardAffiliateCommissionOurMethod'] . '%', intval($p[0]['affiliatecomission']) . '%', $lang[1929]);
	else
		$shareComponent['desc'] = $lang[1929];

if ($__loggedUserDetails['USERID'] > 0 && $__loggedUserDetails['USERID'] != $p[0]['USERID'])
	$shareComponent['title'] = $lang[1928];
else
	$shareComponent['title'] = $lang[135];

$shareComponent['link'] = $affUrl;

/*
* Setup the data for the message modal popup
*/
$customModalData['about'] 				= $pid; // This is an ID which indicates what the message is about (IE: PID)
$customModalData['from'] 				= ''; // Where the user is coming from (IE: an order, WTT, WTB, etc)
$customModalData['recipient']			= $userData['USERID']; // optional, if there is a username
$customModalData['subject'] 			= ''; // optional
$customModalData['CSFR'] 				= $CSFR;
$customModalData['user'] 				= $userData;  // <- comes from conversations_new.php on redictect, user array/results from the database.
$customModalData['droplet'] 			= ''; // Droplet ID to reload
$customModalData['successMessage']		= $lang[3472];
$customModalData['status']				= 0;
$customModalData['title']				= $lang[2766];
$customModalData['username']			= $userData['username'];
$customModalData['img']					= $config['membersprofilepicurl'] . '/' . getProfileImageFromImage($userData['profilepicture']);
$customModalData['rating']				= $userData['rating'];

/*
* Setup the data for the message modal popup
*/
$newMessageModalData['about'] 				= $pid; // This is an ID which indicates what the message is about (IE: PID)
$newMessageModalData['from'] 				= $from; // Where the user is coming from (IE: an order, WTT, WTB, etc)
$newMessageModalData['recipientUserName']	= $userData['username']; // optional, if there is a username
$newMessageModalData['subject'] 			= ''; // optional
$newMessageModalData['CSFR'] 				= $CSFR;
$newMessageModalData['user'] 				= $userData;  // <- comes from conversations_new.php on redictect, user array/results from the database.
$newMessageModalData['droplet'] 			= ''; // Droplet ID to reload
$newMessageModalData['successMessage']		= $lang[2887];

/*
* Setup the data for the report modal popup
*/
$reportComponent['desc'] 				= $lang[3572];
$reportComponent['title'] 				= $lang[245];
$reportComponent['id'] 					= $pid;
$reportComponent['customMessage'] 		= 1;
$reportComponent['type'] 				= 'service';
$reportComponent['action'] 				= '/spam/' . insert_GetEncryptedSpamId(array('id' => $pid)) . '/service';

/**
 * Include the language that we will need
 */
$config['include']['footerJs'] 			.= getLanguageForJS(array(3525, 3472, 3488, 2766, 3777, 3792, 3793, 3846, 3847, 3848, 3849, 3850, 3970, 3971, 3972, 3973));
$config['include']['hdr']['sub-nav'] 	= 1;
$config['include']['photoswipe']		= 1;
$config['include']['includeUploadJs'] 	= 1;

if (userNeedsCaptcha() && intval($__loggedUserDetails['USERID']) > 0)
{
	$config['set_recaptcha'] 			= userNeedsCaptcha();
	$config['include']['doubleCaptcha']	= 1;
}

//Include templates that we need, called in the footer-loader.php file.
$__includeTemplates[0]['path']	= $config['basedir']  . '/views/js/autocomplete/__view-single.js.html';

$config['include']['modals'][0] 		= 'views/modal/view_modal-start-message.php';
$config['include']['modals'][1] 		= 'views/modal/view_modal-custom-order.php';
$config['include']['modals'][2] 		= 'views/modal/view_modal-share.php';
$config['include']['modals'][3] 		= 'views/modal/view_modal-report.php';
$config['include']['modals'][4] 		= 'views/modal/view_modal-coupon-send.php';

/**
 * Subscribe (issue #1338): a buyer puts this listing on a schedule, paid
 * from their balance, from a popup on this page. The popup's script asks
 * the server where the buyer stands; what is decided here is only whether
 * the listing can be subscribed to at all.
 *
 * The identity step verifies in the provider's own modal on this page, so
 * its launcher is attached when the buyer still needs it. Address and phone
 * open in a new tab, which comes back to this listing, and the popup
 * notices when they are done.
 */
$subscribeServiceShown = subscriptionServiceShown($service, intval($__loggedUserDetails['USERID']));

if ($subscribeServiceShown)
{
	$subscribeServicePopup 		= subscriptionServicePopupSetup($p[0]['PID'], $seoUrl);
	$subscribeServiceKyc 		= $subscribeServicePopup['kyc'];
	$subscribeServiceAddressUrl = $subscribeServicePopup['addressUrl'];
	$subscribeServicePhoneUrl 	= $subscribeServicePopup['phoneUrl'];
}

/**
 * If we're staff let's include our report modal
 */
if (isStaff())
{
	$reportComponentDroplet['type'] = 'service';
	$reportComponentDroplet['url'] 	= 'fetch/report';
	$config['include']['modals'][5] = 'views/modal/view_modal-report-droplet.php';
}

/**
 * Logged in users can send custom order requests
 */
if ($__loggedUserDetails['USERID'] > 0)
{
	/***
	 * Get logged in user type
	 */
	$userLoggedStatusArray 	= GetUserTypes($__loggedUserDetails['USERID']);

	if (is_array($userLoggedStatusArray) && in_array('seller', $userLoggedStatusArray)) // null when logged out: 7.4 in_array() returned null
	{
		$query = "SELECT gtitle, PID
						FROM posts
						WHERE USERID = ?
						AND active = 1
						AND type != 99
						";

		$results 	= $conn->execute($query, array($__loggedUserDetails['USERID']));
		$services 	= $results->getrows();
	}
}

/**
 * Set the users footer code if they have any (which could be analytics)
 *
 * 5/17/2019
 */
if (!empty($userData['footer_code'])) $config['include']['footerJs'] .= $userData['footer_code'];

/**
 * Scheme content for google
 */
$schematype 		= 'Product';
$schemaheadline 	= strip_tags($gigHeaderTitle);
$schemacategory 	= $p[0]['name'];

/**
 * The plain URL. view_header.php prints this as og:image and as the
 * JSON-LD "image", and escapes it for each; both need the URL itself.
 * It used to be urlencode()d, which made every service page advertise
 * https%3A%2F%2F... - not a URL, so no share preview anywhere. The
 * one place an encoded copy belongs is a query string (mediaImage).
 *
 * 2026-09-22
 */
$schemaimage 		= listingImageUrl($p[0]['p1']);
$schemadescription 	= formatSchemaText(substr($p[0]['desc_nobbcode'], 0, 250));
$schemarating		= $p[0]['starRating'];
$schematotalratings	= $p[0]['totalRatings'];
$schemaUrl			= $seoUrl;
$schemaproductid	= $p[0]['PID'];

/*
* AMP support removed - 2026-09-22. /amp URLs now 301 to the canonical
* page (see .htaccess and the redirect above); this always renders the
* normal page.
*/
include 'views/header/view_header-loader.php';
include 'views/view_view.php';
include 'views/footer/view_footer-loader.php';
?>