text:
<?php

class COBList
{
    /**
     * Retrieves the CSV file exported by Manage Users and creates the Excel XML COBList
     */
	// Private Variables //
		const iVersion = "3.1.0";
		private $dbUser = array();
		private $hdrUser = array();
		private $dbRes = array();
		private $hdrRes = array();
		private $dbSold = array();
		private $hdrSold = array();
		private $dbGone = array();
		private $hdrGone = array();
		private $dbErr = array();
		private $hdrErr = array();
		private $dbSlip = array();
		private $hdrSlip = array();
		private $hdrWait = array();
		private $dbKayak = array();
		private $hdrKayak = array();
		private $dbRenter = array();
		private $hdrRenter = array();
		private $dbStaff = array();
		private $hdrStaff = array();
		private $dbGridT1 = array();
		private $hdrGridT1 = array();
		private $dbGridT2 = array();
		private $hdrGridT2 = array();
		private $dbVoterT1 = array();
		private $hdrVoterT1 = array();
		private $dbVoterT2 = array();
		private $hdrVoterT2 = array();
		private $dbPets = array();
		private $hdrPets = array();
		private $dbVoter = array();
		private $hdrVoter = array();
		private $dbUnit = array();
		private $hdrUnit = array();
		private $pdo;

	//Constants for fields in User tab
		const iCreated = 0;
		const iUsername = 1;
		const iEnabled = 2;
		const iFirstName = 3;
		const iEmail = 4;
		const iAccess = 5;
		const iUser1LastName = 6;
		const iUnit = 7;
		const iHomePhone = 8;
		const iUser1WorkPhone = 9;
		const iUser1CellPhone = 10;
		const iUser1Occupation = 11;
		const iUser1Employer = 12;
		const iUser1Hobbies = 13;
		const iUser2FirstName = 14;
		const iUser2LastName = 15;
		const iUser2Email = 16;
		const iUser2WorkPhone = 17;
		const iUser2CellPhone = 18;
		const iUser2Occupation = 19;
		const iUser2Employer = 20;
		const iUser2Hobbies = 21;
		const iChild1Name = 22;
		const iChild2Name = 23;
		const iChild3Name = 24;
		const iChild4Name = 25;
		const iChild1Birthdate = 26;
		const iChild2Birthdate = 27;
		const iChild3Birthdate = 28;
		const iChild4Birthdate = 29;
		const iOwner = 30;
		const iMailings = 31;
		const i2ndAddress = 32;
		const iEmergencyContact = 33;
		const iUnitWatcher = 34;
		const iStack = 35;
		const iSlip = 36;
		const iPets = 37;
		const iOfficialVoter = 38;
		const iShowProfile = 39;
		const iShowEmail = 40;
		const iShowPhone = 41;
		const iShowChildren = 42;
		const iAdminNotes = 43;
		const iLastLogin = 44;
		const iUserID = 45;
		const iVoter = 46;
		const iAddress = 47;
		const iCityStateZip = 48;
		const iFloor = 49;

	// indicate whether to show all phone/email or use Profile for external use
	public $showInfo = true;
	// indicate whether full export so trigger all error messages
	public $fullRun = true;
	// indicate whether to log timings
	public $logging = false;

	public function __construct ()
	{
		date_default_timezone_set('America/New_York');
	}

	public function Checkfile(&$checkmsg)
    {
	// called by loadcsv to upload the file and check for format/size & load to dbUser
		$checkmsg="";
		if($_FILES["import"]["error"] > 0){
			$checkmsg =  "Error: " . $_FILES["import"]["error"] . "
";
			$checkmsg .=  "Error on upload. Please click the left menu item to re-run";
			return false;
		}

		// get file info
		$filename = $_FILES["import"]["name"];
		$filetype = $_FILES["import"]["type"];
		$filesize = $_FILES["import"]["size"];
		$tempfile = $_FILES["import"]["tmp_name"];
		$maxsize = 1 * 1024 * 1024;

		// Verify file extension
		$ext = pathinfo($filename, PATHINFO_EXTENSION);
		if(!($ext == "csv")) {
			$checkmsg =  "Error: Please select a CSV file format.";
			return false;
		}
		// Verify file size - 1MB maximum
		if($filesize > $maxsize) {
			$checkmsg =  "Error: Please select a smaller CSV file.";
			return false;
		}
		$temp = file($tempfile);

		// convert to 2 dimensional
		foreach ($temp as $line) {
			$this->dbUser[] = str_getcsv($line);
		}

		// check if exported from website
		if (!(trim($this->dbUser[0][0])=="Created")) {
			$checkmsg =  "Error: '" . strlen($this->dbUser[0][0]) . "' Not valid exported CSV file.";
			return false;
		}
		if (!(count($this->dbUser[0])==45)) {
			$checkmsg =  "Error: " . count($this->dbUser[0]) . " Not valid exported CSV file.";
			return false;
		}

		return true;
	}

	public function ProcessFile(&$checkmsg)
	{
		// Called by loadcsv to process dbUser and build all worksheets
		//open database so the pdo object is available to all functions

		//setup run time logging
		$sTime = microtime(true);
		if (! $this->opendb($checkmsg)) {
			return false;
		}
		if ($this->logging){$this->addError("T00", "DB Open", $this->timeRun($sTime),"","");}
		if ($this->fullRun) {
			// delete all records from UserUnit, Slips & WaitList table and reset owner count in UnitMaster
			$stmt = $this->pdo->query('DELETE FROM UserUnit');
			$stmt = $this->pdo->query('DELETE FROM Slips');
			$stmt = $this->pdo->query('DELETE FROM WaitList');
			$stmt = $this->pdo->query('UPDATE UnitMaster SET count = 0, Owner = "", Renter = "", Voter = "", Address = "", CityStateZip = ""') ;
		}
		if ($this->logging){$this->addError("T10", "DB Clean", $this->timeRun($sTime),"","");}
		// remove header
		$this->hdrUser = array_shift($this->dbUser);

		// extend header for userid, voter, addr, city, floor, lease start,end
		$this->hdrUser[] = "UserID";
		$this->hdrUser[] = "Voter";
		$this->hdrUser[] = "Address";
		$this->hdrUser[] = "CityStateZip";
		$this->hdrUser[] = "Floor";
		//header flipped set to strings
		$keys = array_keys(array_flip($this->hdrUser));
		$this->hdrUser = array_fill_keys($keys, "string");
		// now build proper address fields, floor, etc.
		$this->BuildAddress();
		$this->BuildVoterHdr();
		// now checkdata for errors
		$this->CheckData();
		if ($this->logging){$this->addError("T20", "Start fullRun", $this->timeRun($sTime),"","");}
		if ($this->fullRun) {
			// now checkdata for errors
			$this->BuildListingHdr();
			if ($this->logging){$this->addError("T30", "Start Slip", $this->timeRun($sTime),"","");}
			$this->BuildSlip();
			if ($this->logging){$this->addError("T40", "Start Staff", $this->timeRun($sTime),"","");}
			$this->BuildStaffHdr();
			$this->BuildErrHdr();
			$this->BuildGrids();
			if ($this->logging){$this->addError("T50", "Start Unit", $this->timeRun($sTime),"","");}
			$this->BuildUnit();
		}
		if ($this->logging){$this->addError("T90", "Start CreateFile", $this->timeRun($sTime),"","");}
		// now create Excel file
		$this->CreateFile();
		//now log this run
		$this->LogRun();

		// end of processfile
		unset($this->pdo);

		return true;
	}

	function CheckData() {
	//test for errors
	//Unit format wrong, Stack mismatch, Access mismatch, Phone format, 2nd Address format, Emergency contact phone, Owner access, Mailing blank
	//Offical Voter missing or duplicate

	//step thru User database
	foreach ($this->dbUser as $rowData) {
		if ((stripos($rowData[self::iUnit],'gone') !== false)) {
		} elseif (stripos($rowData[self::iUnit],'sold') !== false) {
		} else {
			// check if not Enabled
			if(($rowData[self::iEnabled] == "No") && (stripos($rowData[self::iEmail],"holdall@gmx")) === false){
					$this->addError('8','Not enabled',$rowData[self::iUnit],$rowData[self::iUser1LastName],'Has email but not enabled');
			}
			// check 2nd address format
			if (count(explode(',',$rowData[self::i2ndAddress]))!==2 && strlen(trim($rowData[self::i2ndAddress])) > 0 ){
				$this->addError('10','2ndAddress format',$rowData[self::iUnit],$rowData[self::iUser1LastName],$rowData[self::i2ndAddress]);
			}
			//check if T1 Pet doesn't have ESA or WSD-ESA
			if (strlen($rowData[self::iPets]) > 0) {
				if (preg_match("/(Tower 1)/", $rowData[self::iUnit])) {
					if (!preg_match("/(WSD)|(ESA)/", $rowData[self::iPets])) {
						$this->addError('6','Missing WSD/ESA',$rowData[self::iUnit],$rowData[self::iUser1LastName],'Need WSD/ESA info');
					}
				}
			}
			//check missing Emergency Contact
			if ($rowData[self::iEmergencyContact] == "") {
				$this->addError('14','Emergency contact',$rowData[self::iUnit],$rowData[self::iUser1LastName],'No Emergency Contact');
			}
			//check missing email address
			if ($rowData[self::iEmail] == "" ){
				$this->addError('2','Email address',$rowData[self::iUnit],$rowData[self::iUser1LastName],'No email address');
			}
			//check phone format
			if(!$this->CheckPhoneFormat($rowData[self::iHomePhone]))
			{
				$this->addError('12','Phone Format - Home',$rowData[self::iUnit],$rowData[self::iUser1LastName],$rowData[self::iHomePhone]);
			}
			if(!$this->CheckPhoneFormat($rowData[self::iUser1CellPhone]))
			{
				$this->addError('12','Phone Format - Cell',$rowData[self::iUnit],$rowData[self::iUser1LastName],$rowData[self::iUser1CellPhone]);
			}
			if(!$this->CheckPhoneFormat($rowData[self::iUser1WorkPhone]))
			{
				$this->addError('12','Phone Format - Work',$rowData[self::iUnit],$rowData[self::iUser1LastName],$rowData[self::iUser1WorkPhone]);
			}

			//check unit format & add owner/voter fields for easy access
			$this->AddToUnitMaster($rowData);
		}
	}
	//check owner count in UnitMaster but not #115
	$query1 = $this->pdo->prepare("SELECT Unit, count, Owner FROM UnitMaster WHERE count = 0 and Unit <> 'Tower 2 # 115'");
	$query1->execute();
	while ($row = $query1->fetch(PDO::FETCH_ASSOC)) {
		$this->addError('1','No unit owner!',$row['Unit'],$row['count'],$row['Owner']);
		}
	$query1 = $this->pdo->prepare("SELECT Unit, count, Owner FROM UnitMaster WHERE count > 1");
	$query1->execute();
	while ($row = $query1->fetch(PDO::FETCH_ASSOC)) {
		$this->addError('16','Owner Count',$row['Unit'],$row['count'],$row['Owner']);
		}
	//check missing Voter in UnitMaster
	$query1 = $this->pdo->prepare("SELECT Unit, Owner, Voter FROM UnitMaster WHERE Voter = '' and Unit <> 'Tower 2 # 115'");
	$query1->execute();
	while ($row = $query1->fetch(PDO::FETCH_ASSOC)) {
		$this->addError('4','Missing Voter',$row['Unit'],$row['Owner'],'Check Official Voter');
		}
	//now query to dbVoter
	$query1 = $this->pdo->prepare("SELECT Voter, Address, CityStateZip, Unit, Bldg
						FROM UnitMaster
						WHERE Unit <> 'Tower 2 # 115'
						ORDER BY Unit");
	$query1->execute();
	$this->dbVoter = $query1->fetchALL(PDO::FETCH_ASSOC);

	}

	function BuildAddress()
	{
	// loop thru db and build calculated fields like addr, voter, floor
	// also build Resident Listing, Staff, Sold, Gone, and Renter arrays
	foreach ($this->dbUser as &$rowData) {
	//skip if gone/sold or staff
		if (stripos($rowData[self::iUnit],'gone') !== false) {
			//build gone db
			$this->dbGone[]=$rowData;
		} elseif (stripos($rowData[self::iUnit],'sold') !== false) {
			//build sold db
			$this->dbSold[]=$rowData;
		} elseif
			(((stripos($rowData[self::iAccess],'^A') !== false) &&
			(stripos($rowData[self::iAccess],'^ADMINWM') == false))||
			($rowData[self::iAccess]=='ADMINEM'))
			{
			// build staff db
			//write row - 6,3,4,5
			$this->dbStaff[]=array($rowData[6],$rowData[3],$rowData[4],$rowData[5]);
		} else {
			// first get voter for this record and add a column
			$rowData[]= $this->GetVoter($rowData);

			// then build addr, citystate for mailings based on user settings
			$temp = $this->GetAddress($rowData);
			$rowData[] = $temp[0];
			$rowData[] = $temp[1];

			// then get floor for all units in this row
			$rowData[] = $this->GetFloor($rowData[self::iUnit]);

			// copy current resident to dbRes
			//write row - 6,3,7,30, 8,10,4,33,34
			$this->dbRes[]=array(
				$rowData[6],$rowData[3],$rowData[7],$rowData[30],$rowData[8],
				$rowData[10],$this->GetEmail($rowData[4]),$rowData[33],$rowData[34]
			);

			// copy certain fields if current renter to dbRenter
			if ($rowData[self::iAccess] == "MEMBER") {
				$temp = $this->GetLeaseDates($rowData);
				//write row - GetLeaseDates(38),6,3,8,10,4,33,34
				$this->dbRenter[]=array(
					$temp[1], $temp[0],
					$rowData[7],$rowData[6],$rowData[3],$rowData[8],
					$rowData[10],$this->GetEmail($rowData[4]),$rowData[33],$rowData[34]
				);
				if ($rowData[self::iOwner] == "Yes") {
					$this->addError('1','Owner Error',$rowData[self::iUnit],$rowData[self::iUser1LastName],'Owner with only member access');
				}
			}
			// copy certain fields to dbPets
			if (strlen(trim($rowData[self::iPets])) > 0) {
				//write row - 7,6,3,37,8 or 10,4,33,34
				// decide if include email and phone based on user Profile settings if showInfo property is set = false
				$phone = "";
				$email = "";
				if ($this->showInfo) {
					$phone = $this->GetBestPhone($rowData);
					$email = $this->GetEmail($rowData[4]);
				} elseif ($rowData[self::iShowProfile]=='Yes') {
						if($rowData[self::iShowPhone]=='Yes') {
							$phone = $this->GetBestPhone($rowData);
						}
						if($rowData[self::iShowEmail]=='Yes') {
							$email = $this->GetEmail($rowData[4]);
						}
					}

				$this->dbPets[]=array(
					$rowData[7],$rowData[6],$rowData[3],$rowData[37], $phone,
					$email,$rowData[33],$rowData[34]
				);
			}
		}
	}
	return;
	}

	function BuildVoterHdr ()
	{
		$this->hdrVoter = array('Name'=>'string', 'Address'=>'string','CityStateZip'=>'string','Unit'=>'string','Bldg'=>'string');

	}
	function BuildListingHdr()
	{
		//setup Listing columns for dbRes & dbRenter that was build in BuildAddress
		$this->hdrRes = array('Last Name'=>'string', 'First Name'=>'string','Unit'=>'string','Owner'=>'string','Home Phone'=>'string','Cell Phone'=>'string','Email'=>'string','Emergency Contact'=>'string','Unit Watcher'=>'string');
		$this->hdrRenter = array('Lease End'=>'string', 'Lease Start'=>'string', 'Unit'=>'string','Last Name'=>'string', 'First Name'=>'string','Home Phone'=>'string','Cell Phone'=>'string','Email'=>'string','Emergency Contact'=>'string','Unit Watcher'=>'string');
		$this->hdrPets = array('Unit'=>'string','Last Name'=>'string', 'First Name'=>'string','Pets & WSD/ESA'=>'string','Phone'=>'string','Email'=>'string','Emergency Contact'=>'string','Unit Watcher'=>'string');
		return;
	}

	function BuildStaffHdr()
	{
		//setup columns for dbStaff that was build in BuildAddress
		$this->hdrStaff=array('Last Name'=>'string', 'First Name'=>'string','Email'=>'string','Access'=>'string');
		return;
	}

	function BuildErrHdr()
	{
		//setup Error columns for dbErr
		$this->hdrErr = array('Level'=>'string', 'Function'=>'string','Unit'=>'string','Name'=>'string','Message'=>'string');
		return;
	}

	function BuildSlip() {
		// scan dbUser to build Slips and Kayak db and arrays
		// build header
		$temp = ($this->showInfo) ? 'Internal':'External';
		$this->hdrSlip = array('Dock'=>'string', 'Slip'=>'string','Class'=>'string','Rate'=>'string','Type'=>'string','Condition'=>'string', 'Last Name'=>'string','Unit'=>'string','Lift'=>'string','Phone'=>'string','Email'=>'string');
		$this->hdrWait = array('Date'=>'string','Name'=>'string','Unit'=>'string','Number'=>'string');

		// step thru each line of the file
		foreach ($this->dbUser as $row) {
			// skip if gone or sold
			if (stripos($row[7],"GONE")!== false) {
				continue;
			}
			if (stripos($row[7],"SOLD")!== false) {
				continue;
			}
			// if no slip then skip
			if (empty($row[36])){
				continue;
			}
			// check if owner has rented their unit and show error

			// explode multiple slips
			$temp = explode (';',$row[36]);
			foreach ($temp as $slip) {
				// check if waitlist and skip
				if (stripos($slip,"W")!== false) {
					// now add to WaitList table
					$wdate = date("Y.m.d",strtotime(substr(trim($slip),2,8)));
					$wnum = "1";
					if (strlen(trim($slip))== 12) {
						$wnum = substr(trim($slip),11,1);
					}
					$sql = "INSERT INTO WaitList (type,unit, names, date, number,userid)
							VALUES ('" . substr(trim($slip),1,1) . "','" . $row[7] . "', '" . $row[6] . "', '" . $wdate . "', '" . $wnum . "', '" . $row[45] . "')";
					// execute the SQL statement - if returns fail then report
					if ($this->pdo->query($sql)){
					} else {
						error_log ("Failed " . $slip );
					}

				} else {
					// if slip has L then strip off L and set lift  = true
					if (stripos($slip,"L")){
						$lift = 1;
						$slip = substr(trim($slip),0,3);
					} else {
						$lift = 0;
					}
					// decide if include email and phone based on user Profile settings if showInfo property is set = false
					$phone = "";
					$email = "";
					if ($this->showInfo) {
						$phone = $this->GetBestPhone($row);
						$email = $this->GetEmail($row[self::iEmail]);
					} elseif ($row[self::iShowProfile]=='Yes') {
							if($row[self::iShowPhone]=='Yes') {
								$phone = $this->GetBestPhone($row);
							}
							if($row[self::iShowEmail]=='Yes') {
								$email = $this->GetEmail($row[self::iEmail]);
							}
						}
					//Check if this slip already assigned to this unit and merge last name
					$sql = "SELECT * FROM Slips WHERE slipid = ?" ;
					$stmt = $this->pdo->prepare ($sql);
					$stmt->execute([trim($slip)]);
					$result = $stmt->fetch();
					if ($result) {
						//found slip exists so check if same unit
						if ($result["unit"] == $row[7]) {
							// same unit so concatenate name if different
							if (!($result["names"] == $row[6])) {
								//different name
								$id = $result["slid"];
								$name = $result["names"] . "/" . $row[6];
								$sql = "UPDATE Slips SET names = ? WHERE slid = ?";
								$this->pdo->prepare($sql)->execute([$name, $id]);
							}
						} else {
							// different unit so insert but add error_get_last
							$this->addError('1','Slip error',$row[self::iUnit],$result["unit"],'Double booked');
							$sql = "INSERT INTO Slips (unit, names, slipid, lift,phone,email,userid)
								VALUES ('" . $row[7] . "', '" . $row[6] . "', '" . trim($slip) . "', '" . $lift . "', '" . $phone . "', '" . $email . "', '" . $row[45] . "')";
							// execute the SQL statement - if returns fail then report
							if ($this->pdo->query($sql)){
							} else {
								echo "Failed " . $slip . "
";
							}

						}
					} else {
						//setup SQL statement for insert to Slips table
						$sql = "INSERT INTO Slips (unit, names, slipid, lift,phone,email,userid)
								VALUES ('" . $row[7] . "', '" . $row[6] . "', '" . trim($slip) . "', '" . $lift . "', '" . $phone . "', '" . $email . "', '" . $row[45] . "')";
						// execute the SQL statement - if returns fail then report
						if ($this->pdo->query($sql)){
						} else {
							error_log ("Failed " . $slip );
						}
					}
				}
			}
		}
		//now query to dbSlip
		$query1 = $this->pdo->prepare("SELECT dock, b.slipid, b.class, rate, b.type, b.condition, a.names, unit, lift, phone, email
							FROM SlipMaster b
							LEFT OUTER JOIN Slips a ON a.slipid = b.slipid
							JOIN RateMaster c ON b.class = c.class
							WHERE b.type = 'Slip' ORDER BY b.slipid");
		$query1->execute();
		$this->dbSlip = $query1->fetchALL(PDO::FETCH_ASSOC);

		//now add waitlist info at bottom
		$this->dbSlip[]=array("");
		$this->dbSlip[]=array("Wait List");
		$this->dbSlip[]=array_keys($this->hdrWait);
		$query1 = $this->pdo->prepare("SELECT date, names, unit FROM WaitList where type = 'S' ORDER BY date");
		$query1->execute();
		$this->dbSlip = array_merge($this->dbSlip,$query1->fetchALL(PDO::FETCH_ASSOC));

		//now query to dbKayak
		$query1 = $this->pdo->prepare("SELECT dock, b.slipid, b.class, rate, b.type, b.condition, a.names, unit, lift, phone, email
							FROM SlipMaster b
							LEFT OUTER JOIN Slips a ON a.slipid = b.slipid
							JOIN RateMaster c ON b.class = c.class
							WHERE b.type = 'Kayak' ORDER BY b.slipid");
		$query1->execute();
		$this->dbKayak = $query1->fetchALL(PDO::FETCH_ASSOC);
		$this->dbKayak[]=array("");
		$this->dbKayak[]=array("Wait List");
		$this->dbKayak[]=array_keys($this->hdrWait);
		$query1 = $this->pdo->prepare("SELECT date, names, unit, number FROM WaitList where type = 'K' ORDER BY date");
		$query1->execute();
		$this->dbKayak = array_merge($this->dbKayak,$query1->fetchALL(PDO::FETCH_ASSOC));

		return;
	}

	function BuildGrids()
	{
		//Build the resident grid
		//setup grid headers
		$this->hdrGridT1 = array("Floor"=>"string","Rembrandt-1"=>"string", "Monet-2"=>"string", "Renoir-3"=>"string", "Van Gogh-4"=>"string", "Van Gogh-5"=>"string", "Renoir-6"=>"string", "Monet-7"=>"string", "Cezanne-8"=>"string");

		$this->hdrGridT2 = array("Floor"=>"string","Rembrandt-9"=>"string", "Renoir-10"=>"string", "Renoir-11"=>"string", "Van Gogh-12"=>"string", "Van Gogh-14"=>"string", "Renoir-15"=>"string", "Renoir-16"=>"string", "Rembrandt-17"=>"string");

		//get owner, renter from db
		$j=0;
		$i=1;
		$query1 = $this->pdo->prepare("SELECT Owner, Renter, Floor, Stack
							FROM UnitMaster
							WHERE Bldg LIKE 'Tower%'
							ORDER BY Floor DESC, Stack ASC");
		$query1->execute();
		// now loop thru all units
		while ($row = $query1->fetch(PDO::FETCH_ASSOC)) {
			$this->dbGridT1[$j][0] = $row['Floor'];
			$this->dbGridT2[$j][0] = $row['Floor'];
			if ($row['Stack']<=8) {
				$this->dbGridT1[$j][$row['Stack']] = $this->AddToGrid($row);
			} elseif ($row['Stack'] == 15 && $row['Floor'] == 1) {
				$this->dbGridT2[$j][6] = " ";
				$this->dbGridT2[$j][7] = $this->AddToGrid($row);
			} elseif ($row['Stack']>12) {
				$this->dbGridT2[$j][$row['Stack']-7] = $this->AddToGrid($row);
			} else {
				$this->dbGridT2[$j][$row['Stack']-8] = $this->AddToGrid($row);
			}
			$i++;
			if ($i>16) {
				$i=1;
				$j++;
			}
		}
	}

	function BuildUnit() {
		// scan dbUser to build UnitUser db and arrays
		// build header
		$temp = ($this->showInfo) ? 'Internal':'External';
		$this->hdrUnit = array('Unit'=>'string', 'Owner'=>'string','Lastname'=>'string','Firstname'=>'string','Email'=>'string','Cellphone'=>'string', 'Homephone'=>'string','Space'=>'string','Emergency Contact'=>'string','Unit Watcher'=>'string');

		// step thru each line of the file
		foreach ($this->dbUser as $row) {
			// skip if gone or sold
			if (stripos($row[7],"GONE")!== false) {
				continue;
			}
			if (stripos($row[7],"SOLD")!== false) {
				continue;
			}
			// if no unit then skip
			if (empty($row[self::iUnit])){
				continue;
			}
			// explode multiple units
			$temp = $this->GetFullUnit($row[self::iUnit]);
			foreach ($temp as $unit) {
				// now add to UnitUser table
				// insert User1
				//check if name is already in db for this unit
				$sql = "SELECT * FROM UserUnit WHERE unit = ? AND lastname = ? AND firstname = ?" ;
				$stmt = $this->pdo->prepare ($sql);
				$stmt->execute(array($row[7],$row[6],$row[3]));
				$result = $stmt->fetch();
				if ($result) {
				} else {
				// decide if include email and phone based on user Profile settings if showInfo property is set = false
				$cphone = "";
				$hphone = "";
				$email = "";
				if ($this->showInfo) {
					$hphone = $row[self::iHomePhone];
					$cphone = $row[self::iUser1CellPhone];
					$email = $this->GetEmail($row[self::iEmail]);
				} elseif ($row[self::iShowProfile]=='Yes') {
						if($row[self::iShowPhone]=='Yes') {
							$hphone = $row[self::iHomePhone];
							$cphone = $row[self::iUser1CellPhone];
						}
						if($row[self::iShowEmail]=='Yes') {
							$email = $this->GetEmail($row[self::iEmail]);
						}
					}
				$sql = "INSERT INTO UserUnit (unit, owner, lastname, firstname, email, cellphone, homephone, emergency, unitwatcher,userid)
						VALUES (:unit, :owner, :lname, :fname, :email, :cell, :home, :emer, :watch, :userid)";
				// execute the SQL statement - if returns fail then report
				$stmt = $this->pdo->prepare ($sql);
				if ($stmt->execute(array(
					"unit" => $unit,
					"owner" => $row[30],
					"lname" => $row[6],
					"fname" => $row[3],
					"email" => $email,
					"cell" => $cphone,
					"home" => $hphone,
					"emer" => substr($row[33],0,50),
					"watch" => substr($row[34],0,50),
					"userid" => $row[45]
					))) {
				} else {
					error_log ("Failed " . $unit );
				}
				}
				// insert User2 if exists
				if (strlen($row[15])>0 ){
					//check if name is already in db for this unit
					$sql = "SELECT * FROM UserUnit WHERE unit = ? AND lastname = ? AND firstname = ?" ;
					$stmt = $this->pdo->prepare ($sql);
					$stmt->execute(array($row[7],$row[15],$row[14]));
					$result = $stmt->fetch();
					if ($result) {
						continue;
					}
					// decide if include email and phone based on user Profile settings if showInfo property is set = false
					$cphone = "";
					$email = "";
					if ($this->showInfo) {
						$cphone = $row[self::iUser2CellPhone];
						$email = $this->GetEmail($row[self::iUser2Email]);
					} elseif ($row[self::iShowProfile]=='Yes') {
							if($row[self::iShowPhone]=='Yes') {
								$cphone = $row[self::iUser2CellPhone];
							}
							if($row[self::iShowEmail]=='Yes') {
								$email = $this->GetEmail($row[self::iUser2Email]);
							}
					}
					$sql = "INSERT INTO UserUnit (unit, owner, lastname, firstname, email, cellphone, homephone, emergency, unitwatcher, userid)
							VALUES (:unit, :owner, :lname, :fname, :email, :cell, :home, :emer, :watch, :userid)";
					// execute the SQL statement - if returns fail then report
					$stmt = $this->pdo->prepare ($sql);
					if ($stmt->execute(array(
						"unit" => $unit,
						"owner" => $row[30],
						"lname" => $row[15],
						"fname" => $row[14],
						"email" => $email,
						"cell" => $cphone,
						"home" => $hphone,
						"emer" => substr($row[33],0,50),
						"watch" => substr($row[34],0,50),
						"userid" => $row[45]
						))) {
					} else {
						error_log ("Failed " . $unit );
					}
				}

		}
		}

		//now query to dbUnit
		$query1 = $this->pdo->prepare("SELECT b.unit, a.owner, a.lastname, a.firstname, a.email, a.cellphone, a.homephone, b.space, a.emergency, a.unitwatcher
							FROM UnitMaster b
							LEFT OUTER JOIN UserUnit a ON a.unit = b.unit
							ORDER BY b.unit");
		$query1->execute();
		$this->dbUnit = $query1->fetchALL(PDO::FETCH_ASSOC);
		return;
	}

	function AddToGrid($row)
	{
		//add owners to front of string and renters in () to back of string and build vVoter array
		$r = $row['Owner'];
		if (strlen($row['Renter'])>0) {
			$r .= " (" . $row['Renter'] . ")";
		}
		return $r;
	}

	function CreateFile()
	{
		// Include the required Class file
		include_once('PAI_xlsxwriter.class.php');
		$filename = "COBList" . date('Ymd') . ".xlsx";
		header('Content-disposition: attachment; filename="'.XLSXWriter::sanitize_filename($filename).'"');
		header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
		header('Content-Transfer-Encoding: binary');
		header('Cache-Control: must-revalidate');
		header('Pragma: public');

		//setup heading row style
		$hstyle = array( 'font'=>'Arial','font-size'=>10,'font-style'=>'bold', 'halign'=>'center', 'border'=>'bottom');
		$h1style = array( 'font'=>'Arial','font-size'=>12,'font-style'=>'bold', 'halign'=>'left', 'border'=>'bottom');

		//sort
		sort($this->dbErr);
		sort($this->dbPets);
		sort($this->dbRenter);

		//write header then sheet data and output file
		$writer = new XLSXWriter();
		$writer->setAuthor('Chris Barlow ' . self::iVersion);
		if ($this->fullRun) {
			$writer->setColWidths('Errors',array(10,20,30,20,40));
			$writer->writeSheetHeader('Errors',$this->hdrErr,true);
			$writer->writeSheetRow('Errors',array(date('m/d/y'),'Condo on the Bay Error Listing'),$h1style);
			$writer->writeSheetRow('Errors',array_keys($this->hdrErr),$hstyle);
			$writer->writeSheet($this->dbErr,'Errors',$this->hdrErr,true);

			$writer->setColWidths('Listing',array(20,15,20,10,15,15,30,30,30));
			$writer->writeSheetHeader('Listing',$this->hdrRes,true);
			$writer->writeSheetRow('Listing',array(date('m/d/y'),'Condo on the Bay Owner & Renter Listing'),$h1style);
			$writer->writeSheetRow('Listing',array_keys($this->hdrRes),$hstyle);
			$writer->writeSheet($this->dbRes,'Listing',$this->hdrRes,true);

			$writer->setColWidths('Units',array(20,10,20,15,30,15,15,10,30,30));
			$writer->writeSheetHeader('Units',$this->hdrUnit,true);
			$temp = ($this->showInfo) ? 'Internal':'External';
			$writer->writeSheetRow('Units',array(date('m/d/y'),$temp,'Condo on the Bay Unit Listing'),$h1style);
			$writer->writeSheetRow('Units',array_keys($this->hdrUnit),$hstyle);
			$writer->writeSheet($this->dbUnit,'Units',$this->hdrUnit,true);

			$writer->setColWidths('Renter',array(12,12,20,15,15,15,15,30,30,30));
			$writer->writeSheetHeader('Renter',$this->hdrRenter,true);
			$writer->writeSheetRow('Renter',array(date('m/d/y'),'Condo on the Bay Renter Listing'),$h1style);
			$writer->writeSheetRow('Renter',array_keys($this->hdrRenter),$hstyle);
			$writer->writeSheet($this->dbRenter,'Renter',$this->hdrRenter,true);

			$writer->setColWidths('Pets WSD-ESA',array(20,15,15,50,15,30,30,30));
			$writer->writeSheetHeader('Pets WSD-ESA',$this->hdrPets,true);
			$writer->writeSheetRow('Pets WSD-ESA',array(date('m/d/y'),$temp,'Condo on the Bay Pets & WSD/ESA Listing'),$h1style);
			$writer->writeSheetRow('Pets WSD-ESA',array_keys($this->hdrPets),$hstyle);
			$writer->writeSheet($this->dbPets,'Pets WSD-ESA',$this->hdrPets,true);

			$writer->setColWidths('Slips',array(12,10,15,10,6,15,15,20,6,20,40));
			$writer->writeSheetHeader('Slips',$this->hdrSlip,true);
			$writer->writeSheetRow('Slips',array(date('m/d/y'),$temp,'Condo on the Bay Slip Listing'),$h1style);
			$writer->writeSheetRow('Slips',array_keys($this->hdrSlip),$hstyle);
			$writer->writeSheet($this->dbSlip,'Slips',$this->hdrSlip,true);

			$writer->setColWidths('Kayak',array(12,10,15,10,6,15,15,20,6,20,40));
			$writer->writeSheetHeader('Kayak',$this->hdrSlip,true);
			$writer->writeSheetRow('Kayak',array(date('m/d/y'),$temp,'Condo on the Bay Kayak Listing'),$h1style);
			$writer->writeSheetRow('Kayak',array_keys($this->hdrSlip),$hstyle);
			$writer->writeSheet($this->dbKayak,'Kayak',$this->hdrSlip,true);

			$writer->setColWidths('Staff',array(20,15,40,40));
			$writer->writeSheetHeader('Staff',$this->hdrStaff,true);
			$writer->writeSheetRow('Staff',array(date('m/d/y'),'Condo on the Bay Staff Listing'),$h1style);
			$writer->writeSheetRow('Staff',array_keys($this->hdrStaff),$hstyle);
			$writer->writeSheet($this->dbStaff,'Staff',$this->hdrStaff,true);

			$writer->setColWidths('Grid T1',array(10,20,20,20,20,20,20,20,20));
			$writer->writeSheetHeader('Grid T1',$this->hdrGridT1,true);
			$writer->writeSheetRow('Grid T1',array(date('m/d/y'),'Condo on the Bay T1 Grid'),$h1style);
			$writer->writeSheetRow('Grid T1',array_keys($this->hdrGridT1),$hstyle);
			$writer->writeSheet($this->dbGridT1,'Grid T1',$this->hdrGridT1,true);

			$writer->setColWidths('Grid T2',array(10,20,20,20,20,20,20,20,20));
			$writer->writeSheetHeader('Grid T2',$this->hdrGridT2,true);
			$writer->writeSheetRow('Grid T2',array(date('m/d/y'),'Condo on the Bay T2 Grid'),$h1style);
			$writer->writeSheetRow('Grid T2',array_keys($this->hdrGridT2),$hstyle);
			$writer->writeSheet($this->dbGridT2,'Grid T2',$this->hdrGridT2,true);
		}

		$writer->setColWidths('Voters',array(30,40,30,20,15));
		$writer->writeSheetHeader('Voters',$this->hdrVoter,true);
		$writer->writeSheetRow('Voters',array_keys($this->hdrVoter),$hstyle);
		$writer->writeSheet($this->dbVoter,'Voters',$this->hdrVoter,false);

		$writer->writeSheetHeader('Users',$this->hdrUser,true);
		$writer->writeSheetRow('Users',array_keys($this->hdrUser),$hstyle);
		$writer->writeSheet($this->dbUser,'Users',$this->hdrUser,false);

		if ($this->fullRun) {
			$writer->writeSheetRow('Sold',array_keys($this->hdrUser),$hstyle);
			$writer->writeSheet($this->dbSold,'Sold',$this->hdrUser,true);

			$writer->writeSheetRow('Gone',array_keys($this->hdrUser),$hstyle);
			$writer->writeSheet($this->dbGone,'Gone',$this->hdrUser,true);
		}
		$writer->writeToStdOut();
		unset($writer);
		return;
	}

// ------ functions called by routines above -------------------------------

	function GetAddress($row) {
	// returns an array of addr, citystate for mailings based on user settings
	if ($row[self::iMailings] == "2nd Address") {
		if ($temp = explode (',',$row[self::i2ndAddress])) {
			if (count($temp)!==2) {
				$temp[0]  = "";
				$temp[1]  = "";
				$this->addError('10','2ndAddress format',$row[self::iUnit],$row[self::iUser1LastName],$row[self::i2ndAddress]);
			}
		}
	} else {
		switch (substr($row[self::iUnit],0, 7)) {
			Case "Tower 1":
				$temp[0] = "888 Blvd of the Arts " . substr($row[self::iUnit],8, 5);
				$temp[1] = "Sarasota FL 34236";
				break;
			Case "Tower 2":
				$temp[0] = "988 Blvd of the Arts " . substr($row[self::iUnit],8, 5);
				$temp[1] = "Sarasota FL 34236";
				break;
			Case "Marina ":
				$temp[0] = substr($row[self::iUnit],16, 3) . " Blvd of the Arts";
				$temp[1] = "Sarasota FL 34236";
				break;
			default:
				$this->addError('1','Unit format',$row[self::iUnit],$row[self::iUser1LastName],'Unit has wrong association');
				$temp[0] = "";
				$temp[1]  = "";
			}
		}
	return $temp;
	}

	function GetVoter($row){
		// gets the name of unit voter
			If ($row[self::iOfficialVoter] == "Resident1") {
				$R = $row[self::iFirstName] . " " . $row[self::iUser1LastName];
			} elseif ($row[self::iOfficialVoter] == "Resident2") {
				$R = $row[self::iUser2FirstName] . " " . $row[self::iUser2LastName];
			} else {
				$R="";
			}
		return $R;
	}

	function GetFloor($unit)
	{
	// REPLACE WITH DB QUERY?
	// return the floor based in the unit. for multi return both floors
	// change to get floor from UnitMaster
	//receives unit string as return common delimited floor(s)
	//unit can be Tower 1 # 708 or Tower 1 #1706/1103 or Tower 1 #1903/04/05 or Tower 1 #1003/Tower 2 # 202

	$Units = explode('/', $unit,5);
	$F= "";
	foreach ($Units as $unit) {
		$unit = trim($unit);
		switch (strlen($unit)) {
			Case 19: //MS
				if (strlen($F)>0) {
					$F = $F . ",1";
				} else {
					$F = "1";
				}
				break;
			Case 13: //full unit
				if (strlen($F)>0) {
					$F = $F . "," . trim(substr($unit, 9, 2));
				} else {
					$F = trim(substr($unit, 9, 2));
				}
				break;
			Case 14:
				if (strlen($F)>0) {
					$F = $F . "," . substr($unit, 10, 2);
				} else {
					$F = substr($unit, 10, 2);
				}
				break;
			Case 4: //unit only
				if (strlen($F)>0) {
					$F = $F . "," . substr($unit, 0, 2);
				} else {
					$F = substr($unit, 1, 2);
				}
				break;
			Case 3: //unit only
				if (strlen($F)>0) {
					$F = $F . "," . substr($unit, 0, 1);
				} else {
					$F = substr($unit, 1, 1);
				}
				break;
			Case 2: //same floor as primary
				$F = $F . "," . substr($F, 0, 2);
				break;
			Case 1:
				$F = $F . "," . substr($F, 0, 1);
				break;
			default:
				$F = strlen($unit);
				$this->addError('1','Unit format',$unit,'','Incorrect format - could not calc floor');
			}
		}
		return $F;
	}

	function LogRun()
	{
		//update RunLog table for this run
		$ip = $_SERVER['REMOTE_ADDR'] ;
		$sql = "INSERT INTO RunLog (ip,type,records, showinfo)
				VALUES ('" . $ip  . "','1','" . count($this->dbUser) . "', '" . intval($this->showInfo) . "')";
		// execute the SQL statement - if returns fail then report
		$this->pdo->query($sql);

		//now email
		$to      = 'webmaster@condoonthebay.com';
		$subject = 'COBList run';
		$message = $sql;
		$headers = 'From: cbarlow@pathfinderassociatesinc.com' . "\r\n" .
			'Reply-To: cbarlow@pathfinderassociatesinc.com' . "\r\n" .
			'X-Mailer: PHP/' . phpversion();
		mail($to, $subject, $message, $headers);

		//error_log($sql,1,$to);

		return ;
	}

	function timeRun ($startTime)
	{
		// returns float of msec since start time. If start time = 0 then return current time
		if ($startTime == 0) {
			return microtime(true);
		} else {
			return microtime(true) - $startTime;
		}
	}

	function addError($level, $function, $unit, $name, $message)
	{
		if ((stripos($unit,'gone') !== false) || (stripos($unit,'sold') !== false)) {
		} else {
			$tmp = array(
				'level' => $level,
				'function'	=> $function,
				'unit'	=> $unit,
				'name'	=> $name,
				'message'	=> $message,
				);
			$this->dbErr[] = $tmp;
		}
	return;
	}

function opendb(&$checkmsg) {
	//function to open PDO database and return PDO object

	// first include file containing host, db, user, password
	include ('COBconnect.php');
	$charset = 'utf8';
	$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
	$opt = [
		PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
		PDO::ATTR_EMULATE_PREPARES   => false,
	];
	try {
		$this->pdo = new PDO($dsn, $user, $pass, $opt);
	} catch (PDOException $e) {
		$checkmsg = 'Connection failed: ' . $e->getMessage();
		return false;
	}
	return true;
}

function AddToUnitMaster ($row) {
	// check unit format against unit master and update owner, owner count, renter, voter fields
	if (strlen($row[self::iUnit])>0) {
		// explode unit
//		$temp = $this->GetFullUnit($row[self::iUnit]);

		if ($temp = $this->GetFullUnit($row[self::iUnit])) {
		$S="";
		foreach ($temp as $u) {
			//check each unit in UnitMaster and confirm Assoc, Floor, Stack
			$sql = "SELECT * FROM UnitMaster WHERE Unit = ?" ;
			$stmt = $this->pdo->prepare ($sql);
			$stmt->execute([trim($u)]);
			$result = $stmt->fetch();
			if ($result) {
				if (stripos($row[self::iOwner],'yes')!== false) {
					//if different owner then increment count in UnitMaster
					if (stripos($result['Owner'],$row[self::iUser1LastName]) !== false) {
					} else {
						$sql = "UPDATE UnitMaster SET Owner = ?, count = count + 1 WHERE Unit = ?" ;
						$stmt = $this->pdo->prepare ($sql);
						if (strlen($result['Owner'])>0) {
							$temp = $result['Owner'] . '/' . $row[self::iUser1LastName];
						} else {
							$temp = $row[self::iUser1LastName];
							}
						$stmt->execute([$temp,trim($u)]);
						}
					//if different voter then update in UnitMaster
					if (stripos($result['Voter'],$row[self::iVoter]) !== false) {
					} elseif (strlen(trim($row[self::iVoter]))>0) {
						$sql = "UPDATE UnitMaster SET Voter = ?, Address = ?, CityStateZip = ? WHERE Unit = ?" ;
						$stmt = $this->pdo->prepare ($sql);
						if (strlen(trim($result['Voter']))>0) {
							$temp = $result['Voter'] . '/' . $row[self::iVoter];
						} else {
							$temp = $row[self::iVoter];
							}
						$stmt->execute([$temp,$row[self::iAddress],$row[self::iCityStateZip],trim($u)]);
						}
					switch ($result['Bldg']) {
					Case "Tower 1":
						if (strpos($row[self::iAccess], '^T1') === false) {
							$this->addError('1','Owner access',$u,$row[self::iUser1LastName],'T1 Owner without ^T1 access');
						}
						//calculate stack
						$S .= $result['Stack'] . ", ";
						break;
					Case "Tower 2":
						if (strpos($row[self::iAccess], '^T2') === false) {
							$this->addError('1','Owner access',$u,$row[self::iUser1LastName],'T2 Owner without ^T2 access');
						}
						$S .= $result['Stack'] . ", ";
						break;
					Case "Marina ":
						if (strpos($row[self::iAccess], '^MS') === false) {
							$this->addError('1','Owner access',$u,$row[self::iUser1LastName],'MS Owner without ^MS access');
						}
						$S .= $result['Stack'] . ", ";
						break;
					}
				} elseif (stripos($row[self::iOwner],"no")!== false) {
					//if different renter then update in UnitMaster
					if (stripos($result['Renter'],$row[self::iUser1LastName]) !== false) {
					} else {
						$sql = "UPDATE UnitMaster SET Renter = ? WHERE Unit = ?" ;
						$stmt = $this->pdo->prepare ($sql);
						if (strlen($result['Renter'])>0) {
							$temp = $result['Renter'] . '/' . $row[self::iUser1LastName];
						} else {
							$temp = $row[self::iUser1LastName];
							}
						$stmt->execute([$temp, trim($u)]);
					}

					//renters should not have owner access
					if (!($row[self::iAccess] == "MEMBER") ) {
						$this->addError('2','Renter error',$u,$row[self::iUser1LastName],'Renter with wrong access');
					}
					$S .= $result['Stack'] . ", ";
				} else {
					//Owner not Yes or No
						$this->addError('2','Owner field',$u,$row[self::iUser1LastName],'Owner field not Yes/No');
				}
			} else {
				// unit not in UnitMaster
					$this->addError('2','Unit error',$u,$row[self::iUser1LastName],'Unit not valid format');

			}
		}
		//check stack after removing last ,
		$S = substr($S, 0, -2);
		if ($row[self::iStack] !== $S && $row[self::iAccess] !== "PUBLIC"  && strlen($S)) {
		//check if moved or sold by Access
			$temp = $row[self::iStack] . " should be " . $S;
			$this->addError('3','Stack error',$row[self::iUnit],$row[self::iUser1LastName],$temp);
		}
	}}
return;
}

function CheckPhoneFormat($p) {
	if (strlen(trim($p))>0) {
		if (!preg_match('/^(\+1|001)?\(?([0-9]{3})\)?([ .-]?)([0-9]{3})([ .-]?)([0-9]{4})/',$p)) {
//		if (!preg_match('\(?[2-9][0-8][0-9]\)?[-. ]?[1-9][0-9]{2}[-. ]?[0-9]{4}',$p)) {
			return false;
		} else {
			return true;
		}
	} else {
		return true;
	}
}

function GetBestPhone($row){
	// function returns cell or other phone
	if (strlen($row[10])>0) {
		return $row[10];
	} elseif (strlen($row[8])>0) {
		return $row[8];
	} elseif (strlen($row[9])>0) {
		return $row[9];
	} else {
		$this->addError('4','No phone number',$row[self::iUnit],$row[self::iUser1LastName],'No phone number for this resident');
		return "";
	}
}
function GetEmail($email){
	// function returns email if not holdall@gmx
	if (stripos($email,"holdall@gmx") === false) {
		return $email;
	}  else {
		return "";
	}
}

function GetLeaseDates($row) {
	// return array of start date and end date and log error
	$D = explode('-', $row[self::iOfficialVoter]);
	// add check for partner v3.0.8
	if ($D[0] == "Partner"){
	//ignore error if partner of owner
		$D[1]="";
	} elseif (count($D) !== 2) {
		$temp = 'Wrong lease date format=' . $row[self::iOfficialVoter];
		$this->addError('5','Lease dates',$row[self::iUnit],$row[self::iUser1LastName],$temp);
		$D[1] = "Error";
	} elseif (strlen($D[0]) > 8) {
		$temp = 'Wrong lease date format=' . $row[self::iOfficialVoter];
		$this->addError('5','Lease dates',$row[self::iUnit],$row[self::iUser1LastName],$temp);
	} else {
		$D[0] = date_format(date_create_from_format("m/d/y",$D[0]),"Y-m-d");
		$D[1] = date_format(date_create_from_format("m/d/y",$D[1]),"Y-m-d");
	}
	return $D;
}

function GetFullUnit($unit) {
	// this returns an array of full units from abbreviated units
	$Units = array_filter(explode('/', $unit));
	$F = "";
	$T = "";
	foreach ($Units as $u) {
		$u = trim($u);
		switch (strlen($u)) {
			Case 19: //MS Marina Suites # 902
				$F[]=$u;
				$T = substr($u,0,15);
				break;
			Case 13: //full u Tower 1 # 708
				$F[]=$u;
				$T = substr($u,0,11);
				break;
			Case 4: //u only 1801
				$F[]= substr($T,0,9) . $u;
				break;
			Case 3: //u only 708
				$F[]= substr($T,0,9) . " " . $u;
				break;
			Case 2: //same floor as primary 02 or 12
				$F[]= $T . $u;
				break;
			Case 1: // 3
				$F[]= $T . "0" . $u;
				break;
			default:
				$this->addError('1','Unit format',$unit,count($F),'Incorrect format - could not calc floor');
				return false;
			}
		}
	return $F;
}
// end of class
}
?>


text:
CDPR might be preparing to add a transmog system to Cyberpunk 2077
It appears that CDPR is working on a feature for Cyberpunk 2077 aimed toward those who hate choosing between functionality and fashion.
by Aron Gerencser published February 23, 2022 February 23, 2022
CD Projekt RED has finally released Cyberpunk 2077 to next-gen consoles. In addition to visual upgrades, the PS5 and Xbox Series S/X versions of Cyberpunk 2077 come with several updates and fixes. Although the verdict is still out on whether Cyberpunk 2077 is finally living up to its potential or not, this hasn't stopped CDPR from working on updates. Case in point, there's evidence of a hidden feature that didn't make it to the final version of patch 1.5.
While it doesn't officially exist for now, players can use Cyberpunk 2077's console to enable the game's hidden "transmog" feature.
According to DJ_Kovrik, there is a hidden feature in Cyberpunk 2077 following the recent update that the in-game files refer to as a "transmog" system. Not only that, but players can even access the said feature using the Game.EnableTransmog() command via Cyberpunk 2077's console. Although it doesn't look fully fleshed out right now, the basics are there - the feature lets players hide the headgear that they are wearing. Of course, because DJ_Kovrik is a modder, he couldn't help but make an offshoot of the said feature. Instead of just working on the face and head, the Hide Your Gear mod hides any piece of gear that players are wearing.
Comment from discussion djkovrik's comment from discussion "Hide Your Gear".
The most interesting part of this half-baked and hidden Cyberpunk 2077 feature is that it has the makings of something more. A "transmog" system suggests that players will eventually be able to keep the aesthetics of a gear they have acquired. This would allow for further customization of what players want to wear and look like in Cyberpunk 2077, which is one of the best features of games like Diablo 3, Outriders and World of Warcraft.
Of course, anything more than the ability to hide headgear in Cyberpunk 2077 is wishful thinking. Even if the word "transmog" suggests a more robust functionality, CDPR probably just went with the name "because".
CDPR still hasn't confirmed when Cyberpunk 2077's next big update is going to arrive.
Having said that, the ability to hide headgear so that you can see the face of your character in Cyberpunk 2077, the face that you spent countless hours crafting, is still nice to have. Just don't expect anything more than what modders have already found. There's a chance that CDPR might improve on the currently existing hidden feature if there's enough demand for it, but it's also highly unlikely that this will happen.
The Polish studio still has a long way to go as far as fixing Cyberpunk 2077 goes. Not to mention, CDPR still hasn't released a substantial story content update that involves more than just fixing and removing things that shouldn't have been in Cyberpunk 2077 in the first place.
Ultimately, fans should just be happy that Cyberpunk 2077 is at least a fully working title right now. The developers certainly seem to think that they've done a good enough job to make Cyberpunk 2077 into an RPG. In addition, CDPR is putting in work on optimizing Cyberpunk 2077 so that Xbox Series S owners can enjoy playing the game with higher framerates.
Cyberpunk 2077's next gen versions is a good start for CDPR's redemption tour.
Cyberpunk, Cyberpunk 2077
Posted by Aron Gerencser
Gaming at least as long as he's been walking, Aron is a fan of all things sci-fi and lover of RPGs. Having written about games for years, he's right at home reporting most of the breaking news in the industry and covering the happenings of the e-sports world. Graduating summa cum laude from Università degli Studi Guglielmo Marconi with a BA in Media Production, Aron has been a game journalist since 2014. When not writing, editing or playing, Aron is building models which you can find on Instagram.

redpajama_set_name: RedPajamaCommonCrawl


Pay in advance the price of one cosmetic laser treatment session by Dec 25, 2018, and receive two 30-minute sessions good January through March 2019.
Order in a few easy steps!
Once purchased, you'll receive an order confirmation number.
Give the gift of perfect legs this year!
*Must purchase treatment "gift" before 12/31/18.
*Must be redeemed between 1/2/19 and 3/29/19.
*Two treatment sessions for the price of one.
* Laser leg vein treatments are appropriate for skin surface spider veins < 1mm diameter.
* Offer cannot be used for other services at Vascular Solutions.


Is it a problem with the graphics in the header that you used?
Is it the content management system (CMS) not publishing correctly?
Is it a CMS template issue because the development team migrated them to a new server?
Could the analytics team have applied a new SEO plugin that caused an issue?
Or is it the infrastructure team that just upgraded the server and it is causing compatibility issues with the webserver?
There are many times when a standard operating procedure is appropriate, but this is a single example where a SOP can make a difference between spending hours trying to determine the problem - with lots of team conference calls and wasted effort - or following a coordinated and documented troubleshooting process that allows you to quickly isolate the problem and address the root cause.
Develop your SOP checklist based on highest-value policy and standards and those that have the largest number of checkpoints across the team or organization.
Develop the SOPs and route them through reviewers and adopt them into the documentation set.
Follow the standards lifecycle for disseminating, implementing and measuring the procedures, ensuring they are available via the central digital repository.
Just like policies and standards, SOPs should be reviewed and updated based on triggers. For that reason, I advise you start off conservatively and grow your procedures as you determine the greatest need and return on investment for the time and effort.
If you find yourself in need of additional guidance on creating SOPs for your organization, feel free to get in touch.


You can find all info and schematic on Runoffgroove website here.
And a version with an added Notch Depth switch.
Did you mean the Notch Switch? I've added it.
Thanks Alex you are a gentleman!
There's no such a software.
We all use DIY Layout Creator to draw them but you need to follow a schematic and decide where to place all the components by yourself.
Thanks Alex, I didn't know about DIY Layout Creator. I will check it out.
WE DONT NEED A COMPUTER PROGRAM WE HAVE ALEX ROCKETT MIROSOL SAVVAS AND IVIARK and the rest of the IvIasterminds making this site the best in the WORLD .
I think there are many auto-routing PCB/schematic apps out there. Usually costing thousands, due to being targeted at commercial and professional uses. There is no auto-routing app for stripboard that i'm aware of.
What comes to DIYLC, i'm hoping someone would fork it and continue its development..
I can't seem to find any information on adjusting the Condor's 100k trimmer...can anyone tell me what target value I'm aiming for with that? Thanks!!!
If I run a distortion box in front of it and then straight to my JamHub, will it sound nice? I'm trying to find a good sound for the 'silent rehearsals' without having to take my amp and with me, only to use the (emulated 412) line out. I now use a behringer sansamp clone (TM300) to emulate an amp with a speaker for my clean sound with a dyna red distortion for crunch.
Verified the notch switch version! Again sounded best to me at 4.5v. Cheers Alex.
Did the etched version that's in rig site a while back. Works well, but is there a way to increase the overall volume? Found myself setting it somewhere over midpoint to get unity volume. Thanks.
*ROG site, sorry 'bout that.
"There is an easy way to get more output level from the circuit involving two resistors around U1A. Place a 100k resistor between pins 1 and 2 of U1A instead of the jumper, and then place a second 100k resistor between pin 2 of U1A and Vref. This will raise overall gain by 6 dB. If you make this second resistor equal to 47k, then you have 10 dB more output signal, while 33k will give you 12 dB's above the standard level. If you go too high with gain you will have clipping in U1A, which is not necessarily a bad thing, because it happens just before the fourth order low-pass filtering which will remove most of the annoying highs! Also, a good point of experimentation is to place a couple of silicon diodes or red LEDs between pins 1 and 2 of U1A, a la Tube Screamer!"
Any reason why this wouldn't work with a 2N5457 instead?
Hi. What's the difference between the circuit with 2 small ICs and the one with 1 big IC? Does this alter the final result in any way? Thank you!
Where are the sw3 and sw4? What is the location of sw numbers?
Neither, it seems. Works exactly like the bias pot that it is. Tweaked it to 4.5V, then fined tuned it by ear.
What are the green and white circles for on the layout?
Simple enough. Thanks for the help!
For those who experienced clipping issues: Omit the input transistor. You don't need it when the Cabsim is placed after your preamp. Just plug the input wire to lug 2 of the trimmer (you don't need the trimmer either).
Then you can place a 100k resistor between pins 1 and 2 of U1A instead of the jumper, and then place a second 100k resistor between pin 2 of U1A and Vref. This will raise some gain you lost by omitting the transistor.


September 14, 2015, Washington, DC – In two interviews with CNN's Carol Costello, Sister Donna Markham, OP, PhD, discusses the topics of poverty in the United States and the role of women in the Catholic Church.
In an interview that aired on September 8, Sister Donna, as the first woman President of Catholic Charities USA (CCUSA), discusses the agency's new campaign, End45: Raise a Hand to End Poverty in America. The former Prioress of the Adrian Dominican Congregation, speaks of the 45 million people in the United States who live in poverty and of our nation's need to address that situation.
During an interview that aired on September 10, Sister Donna discusses with Carol Costello the role of women in the Catholic Church – and the confusion that results from mixed messages on that topic by Pope Francis.
September 15, 2015, Adrian, Michigan – Current and past members of the Portfolio Advisory Board (PAB), Adrian Dominican Sisters and Associates, and members of the general public marked the 40th anniversary of the PAB September 10 with a day of education and an evening program and celebration. The event took place at Weber Retreat and Conference Center on the Motherhouse campus.
Highlighting the day was a special celebratory program (see videos below) detailing the history and the 40-year efforts of the PAB to bring about economic justice through work in corporate responsibility, low-interest loan investment in non-profit community organizations around the world. The PAB also works to educate board members and the community about social justice issues of concern to the Adrian Dominican Sisters.
The program included talks by such speakers as Sister Attracta Kelly, OP, Prioress of the Congregation; Sister Kathleen Schanz, OP, General Council liaison to the PAB; and Dee Joyner, an Adrian Dominican Associate and Chair of the PAB. A 20-minute video featured comments of people from other organizations who collaborated with the PAB over the years. The celebration concluded with a reception.
Throughout the day, participants heard from a variety of speakers. In the opening session, Sarah Power spoke of the efforts of Con-way, an Ann Arbor, Michigan-based freight transportation corporation, to combat human trafficking through its collaboration with Truckers against Trafficking. Sarah told the moving story of one driver for Con-way who, through his vigilant attitude, helped law enforcement to uncover a trafficking ring and bring freedom to 14 women caught in trafficking.
Pat Zerega, of Mercy Investment Services, explained the science behind climate change and discussed the choices that our society can make to lessen its effects. She also addressed ways that religious congregations can work to decrease climate change through such practices as divesting from fossil-fuel companies or engaging in shareholder resolutions.
The afternoon brought speakers from two of the community organizations in which the PAB has invested through low-interest loans. The speakers in both sessions spoke of the positive impact of the Adrian Dominican Sisters' investment in their organizations had on their work and the community they serve.
Leading off was Charles Hammerman, president and CEO of Disability Opportunity Fund (DOF). He gave the Scriptural foundation for caring for people with disabilities and explained the work of DOF. The organization is the first community investment organization to focus on the needs of people with disabilities.
In the closing session of the Day of Education, James Balmer, President of Dawn Farm, and Charles Coleman, coordinator of Dawn Farm's Chapin Street housing project, spoke of their work to offer free recovery and transitional housing programs for people suffering from addictions.
Top photo: Associate Dee Joyner (left), Chair of the Portfolio Advisory Board, and Lura Mack, Executive Director, at the 40th anniversary celebration.


text:
10 November 2020 - Salt Lake City
Committees Thank Members for 'Overwhelming' Response to Hymnbook Revision Efforts
Despite ongoing progress, the Church is still several years away from releasing the new music collections
Hymnbook progress
In June 2018, members around the world were invited to submit original musical numbers to be considered for the new Hymnbook and Children's Songbook. But don't expect to see published books anytime soon. While progress has been made on the revision process, the new music collections are still several years away from being released.© 2020 by Intellectual Reserve, Inc. All rights reserved.
The Hymnbook and Children's Songbook committees have been amazed by the response to the June 2018 global announcement that The Church of Jesus Christ of Latter-day Saints would be revising the current hymnbook and children's songbook.
Members of the Church from 66 countries shared nearly 50,000 suggestions and more than 16,000 original hymns, songs and texts.
Elder Ulisses Soares of the Quorum of the Twelve Apostles and an advisor to the revision committees said the committees never expected such an incredible response from members of the Church. "Their dedication and contributions are humbling!" But don't expect to see published books anytime soon, he said. While progress has been made on the revision process, the new music collections are still several years away from being released.
Original Submissions
In June 2018, the Church made a worldwide announcement regarding the desire to have a new Hymnbook and Children's Songbook that are better suited for a worldwide church. As part of the announcement, Church members were invited to submit original sacred music for consideration.
Steve Schank, Church music manager and chairman of both the Hymnbook Committee and Children's Songbook Committee, said the committees received approximately 16,000 original hymns and songs to consider for the new collections, which surpassed their expectations. "About 55 percent of the submissions were hymn and hymn texts, while the remaining 45 percent were children's songs and children's song texts," Schank said.
"We are grateful for every contribution shared by our members and friends not of our faith, said Elder Jeremy R. Jaggi, General Authority Seventy and an advisor to the committees. "The sheer volume of offerings speaks to the faith and love of each contributor." Elder Jaggi said that all participants should feel satisfied and appreciated for their contributions, but "in the end, we will be able to publish only a small portion of them."
Submission Review Process
The process for reviewing each of the hymns and songs is the same.
A team of dedicated volunteers worked for months to prepare the hymn and song submissions for review, which included removing contributor names from the documents to ensure each submission would be reviewed without bias.
The submissions were then sent out across the world to be prayerfully considered in multiple rounds by many talented musicians and text experts. Seeking the Spirit's guidance, each reviewer provides feedback on how well the submissions meet the criteria for inclusions. The top 2 to 3 percent of the submissions then move on to the Hymnbook and Children's Songbook committees.
Once the committees have reached a consensus on which pieces best meet the goals for sacred music, their recommendations will be reviewed by the advisors of the committees and other Church leaders, including members of each general presidency.
In the end, the First Presidency and the Quorum of the Twelve Apostles will determine which hymns and songs are included in the new music collections.
"We consider every submission to be a sacred offering that members of the Church have laid on the altar," added Sister Cristina B. Franco, second counselor in the Primary general presidency and an advisor to the Children's Songbook Committee. "And whether that offering is published or not, we know that it is an offering of the heart. For that reason, every hymn and song is reverently and prayerfully considered throughout the evaluation process. In the end, many of them may not make it into the new books, but we pray the Lord will bless everyone equally for their offerings."
All individuals who submitted a musical selection for consideration will be informed of the status of their songs when the review process is complete.
Additional Hymn and Song Evaluation
While new hymns and songs will be added to the updated music collections, many current selections will also be republished. At the same time the invitation was made to submit original works, members were also invited to take a survey that documented both positive and negative feedback about current hymns and songs as well as suggestions of new hymns and songs. The committees have reviewed the feedback and suggestions, which have helped to inform their recommendations.
Schank reported that the process of sorting through current hymns and songs has largely been completed, but there is still much to do.
"Initial recommendations for the current hymns and songs have already gone to the senior leaders of the Church, and we have received helpful feedback. Our leaders are very interested and involved in this project and, in many instances, have given detailed and meaningful counsel regarding both the content and the process of the revision," Schank said.
"Meanwhile, the committees have gone on to review hundreds of additional hymns and songs, including music published in the Church magazines over several decades, as well as sacred music currently used in other Christian faith traditions. Administrative review of the new music submissions is complete, but the evaluation process for those selections is ongoing," he said.
Beloved hymns and songs from several cultures, countries and languages are being considered for the new music collections, as well as songs from different faiths that coincide with the teachings of the Church.
"We cannot overstate the power of sacred music to unify the Saints worldwide," said Elder Soares. "Singing from the same body of hymns and songs, all children of God — regardless of ethnicity, country, culture or language — will unite their voices in praise of our common Heavenly Father and His Son, Jesus Christ."
He continued, "The Lord has said, 'The song of the righteous is a prayer unto me, and it shall be answered with a blessing upon their heads' (Doctrine and Covenants 25:12). As we unify our voices as the body of Christ, our Father will bless us by unifying our hearts in unprecedented ways."
A Highly Detailed Work
Hymnbooks in the Church have typically been revised every 30 to 40 years. Because of this, detailed work is required to ensure that the new music collections can best meet the diverse needs of the Church today, Schank said.
Each of the hymns and songs undergo extensive reviews to determine whether any music or text adjustments are necessary. Additionally, copyright contracts must be negotiated for all uses and languages in which the Church publishes sacred music. A new font for lyrics has also been created and will be used in the new music collections to better align with the Church's visual identity efforts. Testing and research to better understand member needs continue to inform all stages of the project.
Though much progress has been made since the revision announcement, Elder Adrián Ochoa, another General Authority Seventy advisor to the revision projects, said the committees have had to adapt to unexpected changes that have altered their thinking about what the new music collections are, what they should achieve and when they will be available.
"Some of the things the committees did not anticipate when this effort started were a number of important changes that have happened in the Church, such as the two-hour block and the home-centered, Church-supported model for worship and learning," Elder Ochoa said. "These inspired directions, as well as adaptations to our work necessitated by the coronavirus situation, will affect both the outcome and the timing of the new collections."
Schank added that the pandemic "reinforced our desire to provide resources along with the hymnbook to make it easier for member to use at home. These could include new resources to help members learn and teach the hymns and songs as well as training materials for members and for those with music callings."
Gratitude for Member Contributions
The deadline has passed for both original song submissions and survey suggestions, and now Schank, on behalf of the committees, expresses appreciation for the time and talents members have so willingly shared to help bless God's children throughout the world.
"The contributions of members have been overwhelming," Schank added, "and we are extremely grateful. Now we are diligently working to respond."
For more information on the hymnbook and children's songbook revisions, visit the Frequently Asked Questions on newmusic.ChurchofJesusChrist.org.
Church Announces Plans for New Hymnbook and Children's Songbook
Major LDS Hymnbook Editions Through the Years
Committees and Strategic Goals Announced for Hymnbook and Children's Songbook Revisions
Previous Article  A Look at How the Church of Jesus Christ Will #LightTheWorld This Christmas Next Article New 'Become' App Helps People Everywhere Grow Closer to God


text:
Ride The City
SDBikeCommuter Web Pages:
Merchants Listing and Info
Adventurecorps
BentRider Online
Bicycle Fixation
Bicycle Parking Online
Bicycling Life
Bike Portland
BikeSD
Bikesnob NYC
Bikey Face
Cafe Mama
Classic Rendezvous
Climber's Crown
Close Call Database
Cosmic Country
Cycling Savvy
Cyclist Lorax (YouTube)
CycloFiend
Forum Smileys
Gypsy By Trade
KPFK Bike Talk
Hum of the City
Ken Kifer
Lazy Randonneur
Los Angeles Cycle Chic
Los Angeles Metro: Bikes Onboard
Lovely Bike
Path Less Pedaled
Pedal Consumption
Prolly Is Not Probably
Randonneurs USA
Retrobike
Riding Pretty
San Diego County Bicycle Coalition
San Diego iCommute
San Diego MTS: Bikes On Trolley and Bus
San Diego Randonneurs
SoCal Region Rocks and Roads
San Diego Velodrome Assoc.
San Francisco Bike Coalition
Stolen Bicycle Registry
Tiny Helmets Big Bikes
Tom Landre's (City of San Diego) Blog
Trademan's Bikes
Velo Cult
Xo-1.org
General: Trip Reports
CommentAuthorGeoff
CommentTimeJun 19th 2012
Feel free to post any trip reports or locations you've visited for biking here.
L.A., S.F., and Portland all have their own separate threads, but I figured we don't need to have a new thread every time we go out-of-area. Plus, I'm lazy, and have multiple places to write about from my recent trips.
First, for those that haven't done an overnight train trip via Amtrak on the Coast Starlight, I highly recommend. It has plenty to do on board, and the sights are amazing in many places. But, this is a bicycling specific forum, and I did indeed take the Brompton with me. We made 10-15 minute stops in a few places (Santa Barbara, San Luis Obispo, San Jose, Oakland, Eugene, Portland, and Tacoma). While the majority of folks who stepped off did so for smoke breaks, I unfolded the bike and spun around the parking lot for exercise and fresh air. Started a ton of conversations (as Mark can attest to) just by bringing it out.
Of all the brief stops, Eugene clearly had the edge for bike stuff near the station. I made a fast loop around the area surrounding us, just soaking up all the corrals, lanes, signs, and BIKES. Bikes EVERYWHERE.
Our first destination was Seattle, which had a solid commuter base. King County has a mandatory helmet law, and I would estimate maybe 2% didn't have a helmet on. The business commuters were not completely spandexed up, but it still wasn't Copenhagen-level casual biking. One day of sun, one day of rain, and the percentage of folks on two wheels stayed about the same. The majority of cyclists integrated well into traffic, and salmon/scofflaws were low in frequency. Sidewalk riding was present, maybe 20% of riders. I'm not sure the legality of riding on the sidewalks in downtown, but no one was accosted or given any guff over it. Whoever said San Diego can't be a bike city because we have too many hills needs to check out this place. The thing that seemed more than cool were the distance signs with little bike symbols on them, letting you know optimum path and distance to different destinations.
Our second destination was Vancouver, BC, Canada, taking the Amtrak Cascades across the border. The baggage train had dedicated bicycle hooks, and was completely full of bikes. There's an extra fee, but obviously is a well-used service. Everything in the area seemed to indicate biking was welcome, but not as many were doing it as Seattle. Also, the place was a rolling REI advertisement. Not a single cyclist (except me and the missus) was without North Face or Columbia apparel. The commuter level dropped off on the rainy day, even though I would qualify it as more of a light mist. Helmets are mandatory in Vancouver, and I only saw one person without. Traffic integration wasn't really easy to spot, since they had bike lanes for uphills, a dedicated path along the sea wall, and cycle tracks in other spots. I'll say the drivers were a an aggressive lot, but that was my experience as a pedestrian (getting honked at in a crosswalk), and observing from a breakfast spot above a busy intersection. Lots of speeding and red light running in the downtown. The word I would use for downtown biking would be "underutilized". This may have less to do with the weather, and more to do with the OUTSTANDING public transportation. Both the light rail and the bus system was fast, frequent, and went EVERYWHERE. Every bus and train car disgorged and picked up tons of riders, moving everyone about efficiently. When you only wait 3 minutes between trains at 11:30 at night (when we got in), you know you have reliable service.
We went for a ride around Stanley Park, which had a rather unique separation for bike/rollerblades and pedestrians. The trip around the perimeter took about an hour. However, there were a lot of interior trails and paths we didn't explore. Again, good bike facilities, and this was where we saw the majority of "recreational" riders, both out for pleasure and spandex rides.
CommentAuthormarkphilips
Thanks for sharing your trip report. I hope to take the Amtrak to the Pacific Northwest.
CommentAuthorbikingbill
Question, does the Coast Starlight run along the coast ... north of San Luis Obispo?
Makes me wonder why we are contemplating an inland high speed rail system that would bypass pretty much every interesting place.
No. Detours inland through Paso Robles until reaching San Jose. Essentially follows the 101 through that region. Once you leave the Bay Area, it heads inland again to connect Sacramento. If you look at the terrain feature on Google maps, it's easy to see why they do it (short answer: hills disagree with steel wheels). It's also the reason why Monterey is skipped in favor of Salinas. I've been on Hwy 1, and agree that Cambria to Monterey is among the most picturesque path in the entire state.
The high speed rail project was originally conceived to link just LA with SF, with few, if any stops. Sacramento and SD were dangled merely to gain support, and SD flat-out crapped on the idea. The current route proposals are reminiscent of the old robber baron days: political favors seem to drive rail spikes to mind-boggling diversions.
CommentAuthorSigurd
Geoff: Feel free to post any trip reports or locations you've visited for biking here. L.A., S.F., and Portland all have their own separate threads, but I figured we don't need to have a new thread every time we go out-of-area.
Great idea for a thread - I will surely use it next time I travel!
And thanks for an excellent travel report.
That route looks far more useful than the stupid Bakersfield plan.
CommentAuthorHans
CommentTimeJun 21st 2012 edited
Spur-of-the-moment trip I decided to take last week. Upper Lake to San Luis Obispo...
Nice (yet sad) that the state acknowledges the screwed up past of California.
After climbing over the range between Lake and Anderson/Mendocino Counties...
Reminded me of France, with grapes everywhere. My Talbot was getting homesick.
I guess in order to appreciate the atmosphere of the vineyards, one should ride on cobblestone bike routes.
Not a motorized vehicle in sight. Henry Cowell Redwoods.
The brewers of Hop-Ottin' Ale laugh at Southern California for not using their occasional sunny days to their fullest.
Nice little contribution the Anderson Valley Brewing Co. does to help bicyclists, is to sponsor these boxes. Contribute what you can, for tubes, repair kits, cables, blinkies.
In the front yard is a drinking fountain, air pump, swing/picnic table, and bike rack (not to mention the antlered bear mascot hiding in the brush). They also took their open land, and installed a disc golf course.
CommentTimeNov 4th 2012
Made a trip down to Paso Robles to the Firestone Walker Brewery and Taproom for their 16th Anniversary Ale release party. For an agricultural town with a large freeway bisecting it, there is a good effort for cycling infrastructure. Heading south from the train station, "Bicycle Route" signage is abundant, making it easy to find the best course through town. Once on Vine St., I got to see their well-done bike lanes, each about 30-40" wide, with thick and smooth brown paint and broad white lane markings.
in areas with a gutter, the bike lane stays wide, not counting the gutter in its standard width. The brown paint made it easy to see debris along the side, but I'm not sure that was by design.
Also, every 1/10th of a mile, there are brickwork strips in the auto lane. Not sure what their intent was, but I can guess it would be to keep the driver focused and keep speeds lower, and a side effect of letting cyclist hear approaching cars.
Given the way-finding signs, I'm led to believe Hwy 46 is a major cycling route. If I had time, I would have rode more around town, but sweet, sweet beer lured me in with her siren song.
CommentTimeNov 10th 2012 edited
Thanks for sharing photos from Paso Robles. When I saw it a couple of years ago it was a sure site to see. Although, we only made a short pitstop in Paso Robles we took the time to ride the "brick red"bike lanes from the plaza entrance and surrounding area.
markphilips: Seen in Paso Robles near a new mall strip.
CommentAuthorsvelocity
CommentTimeNov 11th 2012
Good stuff here guys!
CommentAuthorgavilan
CommentTimeDec 4th 2012
Hey, this is pretty cool!
Hmmm it was said above that the train goes all the way up north but if I check the Amtrak website and try to go to Oakland (to stop in San Fransisco), it says the train goes to Santa Barbara and from there they suggest a bus and then "Capitol Corridor". What gives?
It does go to Seattle (Pacific Surfliner and then Coast Starlight).
Puzzled.
The Coast Starlight is only once a day. You have to catch the first Surfliner out of San Diego (6:05am) to make the connection in Los Angeles. Other options are available with the thruway buses, but that's the only "train only" option. Just search early enough, and make sure you're selecting Oakland-Jack London Square (OKJ) as your destination. Any other Oakland destination isn't train connected.
Your San Francisco connection (if that's your final destination) goes San Diego Surfliner to LA, Coast Starlight to Emeryville (EMY), and then thruway bus to San Francisco. This is especially important because you arrive in the Bay Area in the middle of the night, and may be at a disadvantage in Oakland. The thruway buses only leave from Emeryville, and will be waiting for your arrival.
Geoff: The Coast Starlight is only once a day. You have to catch the first Surfliner out of San Diego (6:05am) to make the connection in Los Angeles. Other options are available with the thruway buses, but that's the only "train only" option. Just search early enough, and make sure you're selecting Oakland-Jack London Square (OKJ) as your destination. Any other Oakland destination isn't train connected.
I don't know why this isn't the route for a high speed rail system. It hits all the major cities.
There are several good reasons for heading inland, but I'll just direct you to these guys for more details.
A recent panel with California High Speed Rail Authority board chairman Dan Richards addresses some of the wave-top points (and recall that prior to his appointment, he told Gov. Brown that the project was "really screwed up" and lobbied for a coastal route, too):
Richard said he changed his mind about the path the train should take because the route must have a terminus in San Francisco, and swinging across from the Altamont would take longer, require a costly bridge crossing and trigger legal challenges.
As far as the Central Valley start, he said the existing stretch of rails from Stockton to Bakersfield sees a million riders a year, which would bolster paying passengers as the system builds up to high-speed rail.
He also emphasized that it's not just about transportation, but also changing cities along the route and revitalizing them by bringing in more density.
There is a separate movement for a revived Coast Daylight route between SF and LA that would address frequency issues with the Coast Starlight. I don't see it as competing with high speed rail because of the different markets they address.
Sweet. Thanks for the tips! We have been talking about doing a "train only" trip up north and just couldn't figure out how. We'll check out the Emeryville connection you suggest.
Geoff: There are several good reasons for heading inland, but I'll just direct you to these guys for more details.
The Coast Starlight hits San Jose, right? This is a huge topic all by itself. Best left alone :-)
CommentTimeJan 2nd 2013 edited
Ojai Valley Trail from Ojai Valley to Ventura.
New Year's eve ride took us along the scenic Ojai Valley Bicycle trail. The trail is approximately 16 miles from Ventura Main st to the idyllic town of Ojai. The trail is under half a mile from the Ventura Amtrak Station (link to bicycling directions). There are campsites close to the trail and we plan to explore that in the future. Parts of this rail trail runs parallel with Hwy 33. We experienced the high contrast between riding beside hwy 33 and the quiet hillside, amazing vista, and serene neighborhoods and parks. The view is just amazing. I highly recommend to start the ride from Ventura with the gentle grade uphill to Ojai. Then be rewarded with a nice downhill back to Ventura. Foster Park is also a good place to start the ride. I noticed several cars parked under a bridge rather than pay the $4 weekend parking fee in the park.
In the town of Ojai, there are two bike shops. The long time Bicycles of Ojai has been around for decades and is highly recommended.
Meiner Oaks Bike shop (MOBshop) recently moved to what used to be an abandoned gasoline station. It was abandoned for nearly 20 years until its re-innovation.
What used to be vehicle hydraulic lift is now a bike rack. What used to be car tire rack is now used for bike tires.
They had a rental fleet of mountain bikes, city bikes, and electric bikes. Below is a mix of rental and for sale bikes. We got a close look of the Yuba Boda Boda and Mundo cargo bikes, Linus city bikes
We stayed at the Lavender Inn historic bed and breakfast which is close to EVERYTHING in town.
Bed, Breakfast and Bikes
Bike among scenic meadows and mountains. The Ojai Valley Trail connects the center of Ojai to Ventura's beach at Seaside Park with a 16.5 mile walking, equestrian and cycling trail. Mountain, road, and tandem bikes, joggers, and child trailers are available for rent. If you bring your own bike, you can lock it safely in our garage. Get $10 off your room, Sunday to Thursday nights only.
CommentAuthorShady John
CommentTimeJul 9th 2013
Spent a couple of days in Sequoia National Park, then a couple days in SLO. Just rode around the campground at Sequoia, but had fun rock crawling on the granite.
In SLO, I finally got a chance to do the Shooters uphill/Morning Glory downhill ride that I've been meaning to tackle for a year or so. Did not crash--very pleased with myself. The mountain bike community in SLO has become really strong, and they're building a (legal) freeride park in "The Eucs." I rode down part of it--much of it walking-- since getting air is not part of my repertoire. I just turned 49, but I saw a couple of mountain bikers that appeared older than I am on that ride---good to see that you don't have to give up the sport just because your hair is turning gray.
Took my kids and two of their cousins on a ride around Cal Poly. Calf number 604 was a big hit up at the dairy unit.
We ended up at Jamba Juice (the original store)--here's a photo of our bikes parked outside. One of my nephews is really tall--guess which one is his bike.
CommentAuthorPaul
CommentTimeJul 10th 2013
^^^ Great stuff Shady. SLO is pretty awesome for any kind of bike. I love how clean and simple that Ibis looks.
CommentAuthorSmorg
What a cool trip, John! :oD I've gotta get me a mountain bike soon. Most of the best views around aren't off from paved roads!
CommentTimeAug 17th 2013
Two weeks ago I went to New Hampshire for a conference. While there I managed to get in a fair bit of riding on a rented 2000 Kona Yee Haw CrMo 700C hybrid bike. The highlight of the riding was a ~55 mile ride that included the climb of Mount Kearsarge, which is one of the bigger climbs on the east coast:
http://www.northeastcycling.com/Mtn_Climbs.html
My GPS blanked out for part of the climb (and has been doing so ever since), so I didn't get Strava credit for the full Kearsarge climb segment. But more than making up for that was the fact that on the day of the climb, the park road was closed to traffic due to construction work at the top. So I had the entire road to myself for both the ascent and the descent. And that was a good thing because I needed the entire road so that I could tack on the steeper sections--30-28 low gear (I think) wasn't low enough with that bike and my already-tired legs. Definitely the longest and hardest ride I've ever done while schlepping a kickstand.
Overall the riding in south-central New Hampshire was pretty good--beautiful scenery, lots of hills, decent (though a bit rough) roads, traffic not too bad. Lots of dirt roads around too--the 622x37 tires were a good fit with the terrain. Weather was beautiful, fortunately. Here are some photos from three days of riding:
Nice, John! :oD My next bike has gotta be a cross or a mountain bike. Missing trail-riding!
Was back in SLO last weekend. Got in three classic rides (imperfectly recorded on Strava due to a balky smartphone). On the longest ride, to the top of West Cuesta Ridge and then down the Shooters trail, I ran out of water (having given my extra bottle to a kid who had been out riding on the ridge since 1 AM with no water--he had just ridden up on a whim in the middle of the night on his way home from work!). Not a big deal, but as I exited Stenner Creek canyon near home, I passed a new building on the outskirts of the Cal Poly campus, and I thought "it would have been really cool if they had put a drinking fountain out here"--and as I rounded the corner, lo and behold there was a drinking fountain--with chiller. There must have been a mountain biker on the design committee.
360 degree panorama from the West Cuesta Ridge Botanical Area
Four of the Nine Sisters viewed from Cuesta Ridge
http://en.wikipedia.org/wiki/Nine_Sisters
View of the upper part of Shooters trail from the ridge
View of Bishop's Peak over the fog from San Luis Mountain (Cerro San Luis). The twin peaks of San Simeon (Pine Mountain) are visible to the left of Bishop's Peak in the distance (~45 miles away).
CommentTimeMar 2nd 2014
I rode the Mt. Vernon Trail in Virginia that runs from Mt. Vernon (duh) to Theodore Roosevelt Island, about 17 miles and change, along the Virginia side of the Potomac River. The trail actually goes a little further and connects with other trails in Northern Virginia and Maryland, but my stop was TR Island.
Entrance archway to Mt. Vernon. The mansion is much bigger.
The trail is well maintained, about two bike-lanes wide asphalt, with numerous wood bridges over the wetlands. There are rolling hills in many spots, but nothing extraordinary. Mostly occupied by joggers and MAMILs on Sunday, the north end of the trail saw more traffic than the southern portion. There are numerous parking lots that provide the only trail access on the southern end, as Washington Memorial Pkwy is a no-bikes-allowed roadway. Many marked and unmarked routes existed once entering Alexandria and continuing north.
Typical trail section
Points of interest along the way include historic old town Alexandria, which would like you to know they consider themselves bike-friendly (actual score: B- to C+)
Smile, you're now a bike advocate
This sign was at a dead-end of the trail. No kidding.
There is a single bike-specific traffic signal at one trail crossing. Sadly, it only goes from red to flashing amber, even when the ped crossing light is on. I guess it's never fully safe to cross on a bike.
I don't know what the middle light is for. It never came on.
Capital Bikeshare starts as far down as Alexandria, and I saw several people using the bikes on the trail.
So that's what a bike share program looks like
The trail accesses Ronald Reagan National Airport, making it possible to bike from the airport to DC for all your national advocacy efforts. The airport website advertises its bike access, and lists parking areas, in case you live in DC and want to ride to the airport.
*cough-cough*
CommentTimeMar 2nd 2014 edited
The Woodrow Wilson Memorial Bridge connects at Alexandria, leading to Maryland. There is a bike and pedestrian crossing on the north edge, making this and many other DC-area bridges that allow bikes to cross instantly better than the Coronado Bridge.
Pretend my camera has a better zoom to see the tiny bikers and joggers
Bizarrely, there was an official Virginia-sanctioned concrete playground under the bridge on the Virginia side. A few basketball courts, some play equipment bolted to the asphalt, and this shimmering row of perfect u-racks, completely empty. Because who the hell bikes to an asphalt playground under the freeway to play basketball?
Finally, the views into the District make the whole trip worth it.
"I'm just a bill. Yes, only a bill..."
Although riding bikes on TR Island is prohibited, cars are prohibited, too, offering a nice little walking trail that makes you forget you're in between two major urban areas.
Teddy Roosevelt, actual size. Brompton shown for scale.
I would have taken more pictures, but I got caught in a downpour on the way back from the Island to Mt. Vernon. On a side note, rain capes are still awesome.
Two drawbacks to the trail -
First and most egregious, EVERY time the path crossed a roadway, there was a stop sign and a sign directing you to dismount and walk across the intersection. Even when the intersection was a two-lane road in a small neighborhood. Seriously, what damn trail building manual recommends these things, who is the imbecile engineer that thought them up, and what logical purpose are they supposed to serve? Can someone look up why these things exist and put it on a kill list? SDCBC? Calbike? Bike League? Anyone?
Second, the wayfinding signs are poorly placed through Alexandria, where the "trail" is actually a blend of path and city streets. I ran into numerous dead ends or just had the trail disappear underneath me several times with nothing to do but backtrack and try a different way. Bayshore Bikeway does a much better job of helping navigate the industrial area through National City. "Bike-Friendly" Alexandria leads the way in poor signage.
A curious aspect of the trail is that it is closed 10pm until 6am... unless you are a "commuter," and then you are exempt. The trail is unlit, so bring good lights for dark hours, as the trail isn't that wide and features plenty of twists and turns. Many signs remind you that the wood bridges freeze before the asphalt trail, and I assume the trail is not cleared the same way the roads are in the area (almost all of the recent snow had melted, save for the ice boulders built by snow plows in various places). This could make it a no-go in icy conditions without a properly outfitted bike.
Overall, a decent trail, offers good connectivity from Alexandria to DC, and makes good use of the Potomac for scenery.


text:
Home>>China Society
Twelve sentenced in China for running porn websites
Twelve people in China have been sentenced to jail terms ranging from 11 months to 11 and a half years after being convicted of running 13 porn websites and spreading obscene articles.
The 12, from different provinces and cities, including Jiangsu, Beijing, Liaoning and Hubei, made 710,000 yuan (104,412 U.S. dollars) in profits from the dissemination of pornography, a court in Sihong County in eastern Jiangsu Province has heard.
Beijing resident Shen Qiang, the founder of the 13 porn websites, was jailed for 11 and a half years, the court ruled.
In April last year, police in Sihong found a local person was participating in the operation of a porn website and traced the evidence to Shen, said Shi Jiman, a police officer in charge of the case, Thursday.
Police later found and detained the other 10 suspects, who were all working for Shen in the northeastern Liaojing Province and the central Hubei Province, he said.
Shen had rented 14 servers from the United States to support the operation of the websites and hired more than 300 people as website administrators. Those websites had registered a total of 10 million members, including 10,000 paying members, he said.
(Editor:王寒露)
China warns 48 websites to clean up porn contents, or face shutdown
China to strengthen Internet regulation in 1st quarter
Healthy virtual world for Chinese minors