Related
I'm working on a project and using firestore with PHP. I created a Firestore class according to documentation below.
https://github.com/GoogleCloudPlatform/php-docs-samples/tree/4149481c23eeb314144ae15c74fa47c5eaf8f3a6/firestore/src
Project is working in localhost with xamp. But it's not working on the server. I didn't understand why it's not working :s Can you help me ?
Thank you.
Getting an error in the getWhere() function on this line :
$documents = $query->documents();
Function getWhere() in Firestore.php :
/**
* Get document(s) with where condition
* #param string $collectionName
* #param string $field
* #param string $operator
* #param $value
* #return array
*/
public function getWhere(string $nerden, string $collectionName, array $whereQuery, string $orderBy = "", string $orderBySort = "desc", int $limit = 0)
{
// echo $nerden . "<br>";
// echo $limit . "<br>";
// echo $orderBySort . "<br>";
// echo $orderBy . "<br>";
// echo "<pre>";
// print_r($whereQuery);
// echo "</pre>";
$arr = [];
$collectionRef = $this->db->collection($collectionName);
$query = null;
foreach ($whereQuery as $param) {
# code...
if ($query == null) {
$query = $collectionRef->where($param["field"], $param["operator"], $param["value"]);
} else {
$query = $query->where($param["field"], $param["operator"], $param["value"]);
}
}
if ($query != null) {
if ($orderBy != "") {
$query = $query->orderBy($orderBy, $orderBySort);
}
if ($limit > 0) {
$query = $query->limit($limit);
}
$documents = $query->documents();
$x = 0;
foreach ($documents as $document) {
if ($document->exists()) {
// printf('Document id for document %s:' . PHP_EOL, $document->id());
// print_r($document->data());
// printf(PHP_EOL);
$arr[$x]["data"] = $document->data();
$arr[$x]["id"] = $document->id();
$x++;
} else {
// printf('Document %s does not exist!' . PHP_EOL, $document->id());
}
}
return $arr;
}
return false;
}
Line 120 in yorumlari_listele.php
$egitici_aktiviteler = yorumlariGetir(1);
Function yorumlariGetir()
// durum parametresine bağlı olarak yorumları getirir
// durum : 0 -> Onay bekleyen yorumları getirir
// durum : 1 -> Onaylanan yorumları getirir
function yorumlariGetir($durum)
{
global $db_firebase;
// Onay bekleyen yorumları getir
$whereQuery = array(
array(
"field" => "durum",
"operator" => "=",
"value" => $durum,
),
);
$sonuc = $db_firebase->getWhere("yorumlariGetir", "reviews", $whereQuery, "tarih", "asc");
return $sonuc;
}
This is FirebaseDb Class:
/*
Yazar : Mustafa SOLAK
Tarih : 14.09.2022 00:02
*/
//require_once "vendor/autoload.php";
use Google\Cloud\Firestore\FirestoreClient;
use Google\Cloud\Firestore\FieldValue;
use Google\Cloud\Core\Timestamp;
use DateTime;
class FirebaseDB
{
private $db;
private $projectId = "*********";
private static $instance = null;
private function __construct()
{
$this->db = new FirestoreClient([
'projectId' => $this->projectId,
]);
// print("Created Cloud Firestore client with project ID: $this->projectId <br>");
}
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new FirebaseDB();
}
return self::$instance;
}
public function getConnection()
{
return $this->db;
}
/**
* Create new document with data
* #param string $collectionName
* #param string $docName
* #param array $data
* #return bool|string
*/
public function newDocument(string $collectionName, string $docName, array $data = [])
{
try {
// set var olanı günceller. yoksa oluşturur.
$this->db->collection($collectionName)->document($docName)->set($data, ["merge" => true]);
return true;
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* Get document and all data with checking for exists
* #param string $collectionName
* #param string $docName
* #return array|null|string
*/
public function getDocument(string $collectionName, string $docName)
{
try {
if (empty($collectionName)) throw new Exception('Collection name missing');
else if (empty($docName)) throw new Exception('Document name missing');
if ($this->db->collection($collectionName)->document($docName)->snapshot()->exists()) {
return $this->db->collection($collectionName)->document($docName)->snapshot()->data();
} else {
throw new Exception('Document are not exists');
}
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* Get document(s) with where condition
* #param string $collectionName
* #param string $field
* #param string $operator
* #param $value
* #return array
*/
public function getWhere(string $nerden, string $collectionName, array $whereQuery, string $orderBy = "", string $orderBySort = "desc", int $limit = 0)
// public function getWhere(string $collectionName, string $field, string $operator, $value)
{
// echo $nerden . "<br>";
// echo $limit . "<br>";
// echo $orderBySort . "<br>";
// echo $orderBy . "<br>";
// echo "<pre>";
// print_r($whereQuery);
// echo "</pre>";
$arr = [];
$collectionRef = $this->db->collection($collectionName);
$query = null;
foreach ($whereQuery as $param) {
# code...
if ($query == null) {
$query = $collectionRef->where($param["field"], $param["operator"], $param["value"]);
} else {
$query = $query->where($param["field"], $param["operator"], $param["value"]);
}
}
if ($query != null) {
if ($orderBy != "") {
$query = $query->orderBy($orderBy, $orderBySort);
}
if ($limit > 0) {
$query = $query->limit($limit);
}
$documents = $query->documents();
$x = 0;
foreach ($documents as $document) {
if ($document->exists()) {
// printf('Document id for document %s:' . PHP_EOL, $document->id());
// print_r($document->data());
// printf(PHP_EOL);
$arr[$x]["data"] = $document->data();
$arr[$x]["id"] = $document->id();
$x++;
} else {
// printf('Document %s does not exist!' . PHP_EOL, $document->id());
}
}
return $arr;
}
return false;
}
/**
* Get documents under the specific collection
* #param string $collectionName
* #return array
*/
public function getCollection(string $collectionName, $orderBy = "", $orderBySort = "")
{
$arr = [];
$collectionRef = $this->db->collection($collectionName);
if ($orderBy != "") {
$query = $collectionRef->orderBy($orderBy, $orderBySort);
$documents = $query->documents();
}else{
$documents = $collectionRef->documents();
}
$x = 0;
foreach ($documents as $document) {
if ($document->exists()) {
// printf('Document id for document %s:' . PHP_EOL, $document->id());
// print_r($document->data());
// printf(PHP_EOL);
$arr[$x]["data"] = $document->data();
$arr[$x]["id"] = $document->id();
$x++;
} else {
// printf('Document %s does not exist!' . PHP_EOL, $document->id());
}
}
return $arr;
}
public function arrayContains()
{
$citiesRef = $this->db->collection('icerikler');
# [START fs_array_membership]
# [START firestore_query_filter_array_contains]
$containsQuery = $citiesRef->where('konu', 'array-contains', '3');
# [END firestore_query_filter_array_contains]
# [END fs_array_membership]
foreach ($containsQuery->documents() as $document) {
printf('Document %s returned by query regions array-contains west_coast' . PHP_EOL, $document->id());
}
}
/**
* Create new collection
* #param string $collectionName
* #param string $docName
* #param array $data
* #return bool|string
*/
public function newCollection(string $collectionName, string $docName, array $data = [])
{
try {
$this->db->collection($collectionName)->document($docName)->create($data);
return true;
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* checks whether collection is exists or not
* #param string $collectionName
* #return bool|string
*/
public function isCollectionExists(string $collectionName)
{
try {
$documents = $this->db->collection($collectionName)->limit(1)->documents();
// it means collection exists
if (!$documents->isEmpty())
return 1;
// oops! collection does not exists
else
return -1;
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* Drop exists document in collection
* #param string $collectionName
* #param string $docName
* #return void
*/
public function dropDocument(string $collectionName, string $docName)
{
try {
$this->db->collection($collectionName)->document($docName)->delete();
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* Update a document with increment operation
* Increases Field value if you use positive number
* Decreases Field value if you use negative number
* By the way you can use this function both operations
* #param string $collectionName
* #param string $docName
* #param string $field -> to be updated
* #param string $amount -> increment amount
* #return void
*/
public function fieldIncrementOrDecrement(string $collectionName, string $docName, $field, $amount)
{
$cityRef = $this->db->collection($collectionName)->document($docName);
try {
$cityRef->update([
['path' => $field, 'value' => FieldValue::increment($amount)]
]);
} catch (Exception $exception) {
return $exception->getMessage();
}
}
public function updateField(string $collectionName, string $docName, $field, $value)
{
try {
$cityRef = $this->db->collection($collectionName)->document($docName);
$cityRef->update([
['path' => $field, 'value' => $value]
]);
} catch (Exception $exception) {
return $exception->getMessage();
}
}
/**
* Set review count data
* #param string $collectionName
* #param string $docName
* #param array $data
* #return bool|string
*/
public function setReviewCounts($collectionName, $docName, $data, $field, $value)
{
try {
$data[$field] = FieldValue::increment($value);
// set var olanı günceller. yoksa oluşturur.
$this->db->collection($collectionName)->document($data["id"] . "_" . $data["type"])->set($data, ["merge" => true]);
return true;
} catch (Exception $exception) {
return $exception->getMessage();
}
}
}
Error Text :
Fatal error: Uncaught RuntimeException: cannot handle unknown field collection_Id on message google.firestore.v1.StructuredQuery.CollectionSelector in /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php:391 Stack trace: #0 /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php(369): Google\ApiCore\Serializer->decodeMessageImpl() #1 /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php(413): Google\ApiCore\Serializer->decodeElement() #2 /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php(127): Google\ApiCore\Serializer->decodeMessageImpl() #3 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Connection/Grpc.php(215): Google\ApiCore\Serializer->decodeMessage() #4 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Query.php(204): Google\Cloud\Firestore\Connection\Grpc->runQuery() #5 [internal function]: Google\Cloud\Firestore\Query->Google\Cloud\Firestore{closure}() #6 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Core/src/ExponentialBackoff.php(80): call_user_func_array() #7 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Query.php(244): Google\Cloud\Core\ExponentialBackoff->execute() #8 /home/evok9367/public_html/mustafasolakweb/Firestore.php(127): Google\Cloud\Firestore\Query->documents() #9 /home/evok9367/public_html/mustafasolakweb/screens/yorumlar/yorumlar_listele.php(329): FirebaseDB->getWhere() #10 /home/evok9367/public_html/mustafasolakweb/screens/yorumlar/yorumlar_listele.php(120): yorumlariGetir() #11 /home/evok9367/public_html/mustafasolakweb/index.php(33): include('/home/evok9367/...') #12 {main} Next Google\ApiCore\ValidationException: Error decoding message: cannot handle unknown field collection_Id on message google.firestore.v1.StructuredQuery.CollectionSelector in /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php:129 Stack trace: #0 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Connection/Grpc.php(215): Google\ApiCore\Serializer->decodeMessage() #1 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Query.php(204): Google\Cloud\Firestore\Connection\Grpc->runQuery() #2 [internal function]: Google\Cloud\Firestore\Query->Google\Cloud\Firestore{closure}() #3 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Core/src/ExponentialBackoff.php(80): call_user_func_array() #4 /home/evok9367/public_html/mustafasolakweb/vendor/google/cloud/Firestore/src/Query.php(244): Google\Cloud\Core\ExponentialBackoff->execute() #5 /home/evok9367/public_html/mustafasolakweb/Firestore.php(127): Google\Cloud\Firestore\Query->documents() #6 /home/evok9367/public_html/mustafasolakweb/screens/yorumlar/yorumlar_listele.php(329): FirebaseDB->getWhere() #7 /home/evok9367/public_html/mustafasolakweb/screens/yorumlar/yorumlar_listele.php(120): yorumlariGetir() #8 /home/evok9367/public_html/mustafasolakweb/index.php(33): include('/home/evok9367/...') #9 {main} thrown in /home/evok9367/public_html/mustafasolakweb/vendor/google/gax/src/Serializer.php on line 129
There are some Turkish words in the code and I added English translations, hope it helps.
nerden = fromWhere
yorumlari_listele = list_comments
egitici_aktiviteler = activities
yorumlariGetir = getComments
durum = state
sonuc = result
tarih = date
I tried all the things, changed the code like
https://github.com/GoogleCloudPlatform/php-docs-samples/tree/4149481c23eeb314144ae15c74fa47c5eaf8f3a6/firestore/src
I would like to set "joomla_remember_me..." cookie from my custom script depending on condition. I would probably need to execute public function onUserAfterLogin from the file \plugins\authentication\cookie\cookie.php with the argument "remember me" set, but I cannot figure out how.
Here's the cookie.php file for the reference:
/**
* Joomla Authentication plugin
*
* #since 3.2
* #note Code based on http://jaspan.com/improved_persistent_login_cookie_best_practice
* and http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/
*/
class PlgAuthenticationCookie extends JPlugin
{
/**
* Application object
*
* #var JApplicationCms
* #since 3.2
*/
protected $app;
/**
* Database object
*
* #var JDatabaseDriver
* #since 3.2
*/
protected $db;
/**
* Reports the privacy related capabilities for this plugin to site administrators.
*
* #return array
*
* #since 3.9.0
*/
public function onPrivacyCollectAdminCapabilities()
{
$this->loadLanguage();
return array(
JText::_('PLG_AUTHENTICATION_COOKIE') => array(
JText::_('PLG_AUTH_COOKIE_PRIVACY_CAPABILITY_COOKIE'),
)
);
}
/**
* This method should handle any authentication and report back to the subject
*
* #param array $credentials Array holding the user credentials
* #param array $options Array of extra options
* #param object &$response Authentication response object
*
* #return boolean
*
* #since 3.2
*/
public function onUserAuthenticate($credentials, $options, &$response)
{
// No remember me for admin
if ($this->app->isClient('administrator'))
{
return false;
}
// Get cookie
$cookieName = 'joomla_remember_me_' . JUserHelper::getShortHashedUserAgent();
$cookieValue = $this->app->input->cookie->get($cookieName);
// Try with old cookieName (pre 3.6.0) if not found
if (!$cookieValue)
{
$cookieName = JUserHelper::getShortHashedUserAgent();
$cookieValue = $this->app->input->cookie->get($cookieName);
}
if (!$cookieValue)
{
return false;
}
$cookieArray = explode('.', $cookieValue);
// Check for valid cookie value
if (count($cookieArray) !== 2)
{
// Destroy the cookie in the browser.
$this->app->input->cookie->set($cookieName, '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
JLog::add('Invalid cookie detected.', JLog::WARNING, 'error');
return false;
}
$response->type = 'Cookie';
// Filter series since we're going to use it in the query
$filter = new JFilterInput;
$series = $filter->clean($cookieArray[1], 'ALNUM');
// Remove expired tokens
$query = $this->db->getQuery(true)
->delete('#__user_keys')
->where($this->db->quoteName('time') . ' < ' . $this->db->quote(time()));
try
{
$this->db->setQuery($query)->execute();
}
catch (RuntimeException $e)
{
// We aren't concerned with errors from this query, carry on
}
// Find the matching record if it exists.
$query = $this->db->getQuery(true)
->select($this->db->quoteName(array('user_id', 'token', 'series', 'time')))
->from($this->db->quoteName('#__user_keys'))
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series))
->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName))
->order($this->db->quoteName('time') . ' DESC');
try
{
$results = $this->db->setQuery($query)->loadObjectList();
}
catch (RuntimeException $e)
{
$response->status = JAuthentication::STATUS_FAILURE;
return false;
}
if (count($results) !== 1)
{
// Destroy the cookie in the browser.
$this->app->input->cookie->set($cookieName, '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
$response->status = JAuthentication::STATUS_FAILURE;
return false;
}
// We have a user with one cookie with a valid series and a corresponding record in the database.
if (!JUserHelper::verifyPassword($cookieArray[0], $results[0]->token))
{
/*
* This is a real attack!
* Either the series was guessed correctly or a cookie was stolen and used twice (once by attacker and once by victim).
* Delete all tokens for this user!
*/
$query = $this->db->getQuery(true)
->delete('#__user_keys')
->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($results[0]->user_id));
try
{
$this->db->setQuery($query)->execute();
}
catch (RuntimeException $e)
{
// Log an alert for the site admin
JLog::add(
sprintf('Failed to delete cookie token for user %s with the following error: %s', $results[0]->user_id, $e->getMessage()),
JLog::WARNING,
'security'
);
}
// Destroy the cookie in the browser.
$this->app->input->cookie->set($cookieName, '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
// Issue warning by email to user and/or admin?
JLog::add(JText::sprintf('PLG_AUTH_COOKIE_ERROR_LOG_LOGIN_FAILED', $results[0]->user_id), JLog::WARNING, 'security');
$response->status = JAuthentication::STATUS_FAILURE;
return false;
}
// Make sure there really is a user with this name and get the data for the session.
$query = $this->db->getQuery(true)
->select($this->db->quoteName(array('id', 'username', 'password')))
->from($this->db->quoteName('#__users'))
->where($this->db->quoteName('username') . ' = ' . $this->db->quote($results[0]->user_id))
->where($this->db->quoteName('requireReset') . ' = 0');
try
{
$result = $this->db->setQuery($query)->loadObject();
}
catch (RuntimeException $e)
{
$response->status = JAuthentication::STATUS_FAILURE;
return false;
}
if ($result)
{
// Bring this in line with the rest of the system
$user = JUser::getInstance($result->id);
// Set response data.
$response->username = $result->username;
$response->email = $user->email;
$response->fullname = $user->name;
$response->password = $result->password;
$response->language = $user->getParam('language');
// Set response status.
$response->status = JAuthentication::STATUS_SUCCESS;
$response->error_message = '';
}
else
{
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = JText::_('JGLOBAL_AUTH_NO_USER');
}
}
/**
* We set the authentication cookie only after login is successfullly finished.
* We set a new cookie either for a user with no cookies or one
* where the user used a cookie to authenticate.
*
* #param array $options Array holding options
*
* #return boolean True on success
*
* #since 3.2
*/
public function onUserAfterLogin($options)
{
// No remember me for admin
if ($this->app->isClient('administrator'))
{
return false;
}
if (isset($options['responseType']) && $options['responseType'] === 'Cookie')
{
// Logged in using a cookie
$cookieName = 'joomla_remember_me_' . JUserHelper::getShortHashedUserAgent();
// We need the old data to get the existing series
$cookieValue = $this->app->input->cookie->get($cookieName);
// Try with old cookieName (pre 3.6.0) if not found
if (!$cookieValue)
{
$oldCookieName = JUserHelper::getShortHashedUserAgent();
$cookieValue = $this->app->input->cookie->get($oldCookieName);
// Destroy the old cookie in the browser
$this->app->input->cookie->set($oldCookieName, '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
}
$cookieArray = explode('.', $cookieValue);
// Filter series since we're going to use it in the query
$filter = new JFilterInput;
$series = $filter->clean($cookieArray[1], 'ALNUM');
}
elseif (!empty($options['remember']))
{
// Remember checkbox is set
$cookieName = 'joomla_remember_me_' . JUserHelper::getShortHashedUserAgent();
// Create a unique series which will be used over the lifespan of the cookie
$unique = false;
$errorCount = 0;
do
{
$series = JUserHelper::genRandomPassword(20);
$query = $this->db->getQuery(true)
->select($this->db->quoteName('series'))
->from($this->db->quoteName('#__user_keys'))
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series));
try
{
$results = $this->db->setQuery($query)->loadResult();
if ($results === null)
{
$unique = true;
}
}
catch (RuntimeException $e)
{
$errorCount++;
// We'll let this query fail up to 5 times before giving up, there's probably a bigger issue at this point
if ($errorCount === 5)
{
return false;
}
}
}
while ($unique === false);
}
else
{
return false;
}
// Get the parameter values
$lifetime = $this->params->get('cookie_lifetime', 60) * 24 * 60 * 60;
$length = $this->params->get('key_length', 16);
// Generate new cookie
$token = JUserHelper::genRandomPassword($length);
$cookieValue = $token . '.' . $series;
// Overwrite existing cookie with new value
$this->app->input->cookie->set(
$cookieName,
$cookieValue,
time() + $lifetime,
$this->app->get('cookie_path', '/'),
$this->app->get('cookie_domain', ''),
$this->app->isHttpsForced(),
true
);
$query = $this->db->getQuery(true);
if (!empty($options['remember']))
{
// Create new record
$query
->insert($this->db->quoteName('#__user_keys'))
->set($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username))
->set($this->db->quoteName('series') . ' = ' . $this->db->quote($series))
->set($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName))
->set($this->db->quoteName('time') . ' = ' . (time() + $lifetime));
}
else
{
// Update existing record with new token
$query
->update($this->db->quoteName('#__user_keys'))
->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username))
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series))
->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName));
}
$hashedToken = JUserHelper::hashPassword($token);
$query->set($this->db->quoteName('token') . ' = ' . $this->db->quote($hashedToken));
try
{
$this->db->setQuery($query)->execute();
}
catch (RuntimeException $e)
{
return false;
}
return true;
}
/**
* This is where we delete any authentication cookie when a user logs out
*
* #param array $options Array holding options (length, timeToExpiration)
*
* #return boolean True on success
*
* #since 3.2
*/
public function onUserAfterLogout($options)
{
// No remember me for admin
if ($this->app->isClient('administrator'))
{
return false;
}
$cookieName = 'joomla_remember_me_' . JUserHelper::getShortHashedUserAgent();
$cookieValue = $this->app->input->cookie->get($cookieName);
// There are no cookies to delete.
if (!$cookieValue)
{
return true;
}
$cookieArray = explode('.', $cookieValue);
// Filter series since we're going to use it in the query
$filter = new JFilterInput;
$series = $filter->clean($cookieArray[1], 'ALNUM');
// Remove the record from the database
$query = $this->db->getQuery(true)
->delete('#__user_keys')
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series));
try
{
$this->db->setQuery($query)->execute();
}
catch (RuntimeException $e)
{
// We aren't concerned with errors from this query, carry on
}
// Destroy the cookie
$this->app->input->cookie->set($cookieName, '', 1, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain', ''));
return true;
}
}
Thanks in advance :)
It's a very complex process and I think you would be better off cloning this plugin to a new name, adding your condition to the function, unpublish the current plugin and publish your new one instead. You will also have to watch for any changes to the original plugin in case a security issue is found.
I have a PHP script that synchronize a local directory with a remote one ...
[...]
$ftp = new Ftp('11.222.11.222', 'the_user', 'the_pass');
$ftp->syncFolder('userfiles/project', '/web/userfiles/project', true);
[...]
And a Class
class Ftp
{
public $conn;
private $url;
private $user;
private $password;
private $loged;
/**
* Ftp constructor.
* #param $url
* #param $user
* #param $password
* #throws Exception
*/
public function __construct($url, $user, $password)
{
$this->conn = ftp_connect($url);
$this->user = $user;
$this->password = $password;
if ($this->ftp_login($this->user, $this->password)) {
$this->loged = true;
} else {
$this->loged = false;
}
$this->ftp_pasv(true);
}
/**
* #param $func
* #param $a
* #return mixed
* #throws Exception
*/
public function __call($func, $a)
{
if (strstr($func, 'ftp_') !== false && function_exists($func)) {
array_unshift($a, $this->conn);
return call_user_func_array($func, $a);
} else {
if (method_exists($this, $func)) {
return call_user_func_array($func, $a);
} else {
throw new Exception('Undefine Class Function');
}
}
}
/**
* #param $local
* #param $server
* #param $verbose
* #return bool
* #throws Exception
*/
function syncFolder($local, $server, $verbose)
{
if (!$this->loged) {
return false;
}
$this->ftp_chdir($server);
$server_list = $this->ftp_nlist('.');
$local_list = scandir(BASE_PATH . $local);
foreach ($server_list as $item) {
if ($item == '.' || $item == '..') {
continue;
}
$folder = $this->ftp_nlist($item);
if ($folder[0] == '.') {
if (!in_array($item, $local_list)) {
$this->emptyDir($server . '/' . $item, $verbose);
$this->ftp_chdir($server);
} else {
$this->syncFolder($local . '/' . $item, $server . '/' . $item, $verbose);
$this->ftp_chdir($server);
}
} else {
if (!in_array($item, $local_list)) {
if ($this->ftp_delete($item)) {
} else {
}
} else {
$stats = stat(BASE_PATH . $local . '/' . $item);
if ($this->ftp_mdtm($item) < $stats['mtime']) {
if ($this->ftp_put($item, BASE_PATH . $local . '/' . $item, FTP_BINARY)) {
} else {
}
} else {
}
}
}
}
$this->putMissingFilesFromDir($local, $server, $verbose);
$this->ftp_chdir($server);
}
/**
* #param $dir
* #param $verbose
* #return bool
* #throws Exception
*/
function emptyDir($dir, $verbose)
{
if (!$this->loged) {
return false;
}
$this->ftp_chdir($dir);
$server_list = $this->ftp_nlist('.');
foreach ($server_list as $item) {
if ($item == '.' || $item == '..') {
continue;
}
$folder = $this->ftp_nlist($item);
if ($folder[0] == '.') {
$this->emptyDir($dir . '/' . $item, $verbose);
$this->ftp_chdir($dir);
} else {
if ($this->ftp_delete($item)) {
} else {
}
}
}
if ($this->ftp_rmdir($dir)) {
} else {
}
}
/**
* #param $local
* #param $server
* #param $verbose
* #return bool
* #throws Exception
*/
function putMissingFilesFromDir($local, $server, $verbose)
{
if (!$this->loged) {
return false;
}
$this->ftp_chdir($server);
$server_list = $this->ftp_nlist('.');
$local_list = scandir(BASE_PATH . $local);
foreach ($local_list as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (is_dir(BASE_PATH . $local . '/' . $item)) {
if (!in_array($item, $server_list)) {
$this->ftp_mkdir($item);
}
$this->putMissingFilesFromDir($local . '/' . $item, $server . '/' . $item, $verbose);
$this->ftp_chdir($server);
} else {
if (!in_array($item, $server_list)) {
if ($this->ftp_put($item, BASE_PATH . $local . '/' . $item, FTP_BINARY)) {
} else {
}
}
}
}
}
}
This script works fine from browser and from windows' CMD using this command:
C:\php5.6\php.exe -f "D:\www\project\cron\synchro_hosting.php"
But when I try to run this script from a batch file in a scheduled task the ftp_connect function doesn't do anything. It remains waiting for something.
cd /D "D:\www\project\cron\"
"C:\php5.6\php.exe" -f synchro_hosting.php
I'm using a log file to write events (now removed for summarize the code) and the code stop in ftp_connect.
Ideas?
UPDATE 1:
I tried to run the same command from CMD, batch file triggered from CMD and a batch file triggered from scheduled task, and works fine with two first options. The execution stops in ftp_connect function when is triggered from batch file in a scheduled task. The windows user that execute this actions is the same in the three cases.
C:\php5.6\php.exe -f "D:\www\project\cron\synchro_hosting.php"
I don't know where is the problem with ftp_connect from scheduled task. Is maybe a permissions issue?
There are another way in Windows to trigger an php script recurrently?
As I told to Rockstar, I have found a solution to avoid the problem, not to fix it. I tried to execute a php file directly from scheduled tasks and it worked instead of execute a bat file.
The action tab from scheduled tasks should look like this:
Action: Start a program
Program or Script: C:\php5.6\php.exe
Add arguments: "D:\www\project\cron\synchro_hosting.php"
Action Tab from Scheduled Tasks
My host confirmed that Zend 2.3 is installed. I then proceeded to check the installation was working by uploading this script:
http://www.jarodmorris.com/zend_check.php
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license#zend.com so we can send you a copy immediately.
*
* #category Zend
* #package Zend_Gdata
* #subpackage Demos
* #copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* #license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* Simple class to verify that the server that this is run on has a correct
* installation of the Zend Framework Gdata component.
*/
class InstallationChecker {
const CSS_WARNING = '.warning { color: #fff; background-color: #AF0007;}';
const CSS_SUCCESS = '.success { color: #000; background-color: #69FF4F;}';
const CSS_ERROR = '.error { color: #fff; background-color: #FF9FA3;}';
const PHP_EXTENSION_ERRORS = 'PHP Extension Errors';
const PHP_MANUAL_LINK_FRAGMENT = 'http://us.php.net/manual/en/book.';
const PHP_REQUIREMENT_CHECKER_ID = 'PHP Requirement checker v0.1';
const SSL_CAPABILITIES_ERRORS = 'SSL Capabilities Errors';
const YOUTUBE_API_CONNECTIVITY_ERRORS = 'YouTube API Connectivity Errors';
const ZEND_GDATA_INSTALL_ERRORS = 'Zend Framework Installation Errors';
const ZEND_SUBVERSION_URI = 'http://framework.zend.com/download/subversion';
private static $REQUIRED_EXTENSIONS = array(
'ctype', 'dom', 'libxml', 'spl', 'standard', 'openssl');
private $_allErrors = array(
self::PHP_EXTENSION_ERRORS => array(
'tested' => false, 'errors' => null),
self::ZEND_GDATA_INSTALL_ERRORS => array(
'tested' => false, 'errors' => null),
self::SSL_CAPABILITIES_ERRORS => array(
'tested' => false, 'errors' => null),
self::YOUTUBE_API_CONNECTIVITY_ERRORS => array(
'tested' => false, 'errors' => null)
);
private $_sapiModeCLI = null;
/**
* Create a new InstallationChecker object and run verifications.
* #return void
*/
public function __construct()
{
$this->determineIfInCLIMode();
$this->runAllVerifications();
$this->outputResults();
}
/**
* Set the sapiModeCLI variable to true if we are running CLI mode.
*
* #return void
*/
private function determineIfInCLIMode()
{
if (php_sapi_name() == 'cli') {
$this->_sapiModeCLI = true;
}
}
/**
* Getter for sapiModeCLI variable.
*
* #return boolean True if we are running in CLI mode.
*/
public function runningInCLIMode()
{
if ($this->_sapiModeCLI) {
return true;
} else {
return false;
}
}
/**
* Run verifications, stopping at each step if there is a failure.
*
* #return void
*/
public function runAllVerifications()
{
if (!$this->validatePHPExtensions()) {
return;
}
if (!$this->validateZendFrameworkInstallation()) {
return;
}
if (!$this->testSSLCapabilities()) {
return;
}
if (!$this->validateYouTubeAPIConnectivity()) {
return;
}
}
/**
* Validate that the required PHP Extensions are installed and available.
*
* #return boolean False if there were errors.
*/
private function validatePHPExtensions()
{
$phpExtensionErrors = array();
foreach (self::$REQUIRED_EXTENSIONS as $requiredExtension) {
if (!extension_loaded($requiredExtension)) {
$requiredExtensionError = $requiredExtension .
' extension missing';
$documentationLink = null;
if ($requiredExtension != 'standard') {
$documentationLink = self::PHP_MANUAL_LINK_FRAGMENT .
$requiredExtension . '.php';
$documentationLink =
$this->checkAndAddHTMLLink($documentationLink);
} else {
$documentationLink = self::PHP_MANUAL_LINK_FRAGMENT .
'spl.php';
$documentationLink =
$this->checkAndAddHTMLLink($documentationLink);
}
if ($documentationLink) {
$phpExtensionErrors[] = $requiredExtensionError .
' - refer to ' . $documentationLink;
}
}
}
$this->_allErrors[self::PHP_EXTENSION_ERRORS]['tested'] = true;
if (count($phpExtensionErrors) > 0) {
$this->_allErrors[self::PHP_EXTENSION_ERRORS]['errors'] =
$phpExtensionErrors;
return false;
}
return true;
}
/**
* Validate that the Gdata component of Zend Framework is installed
* properly. Also checks that the required YouTube API helper methods are
* found.
*
* #return boolean False if there were errors.
*/
private function validateZendFrameworkInstallation()
{
$zendFrameworkInstallationErrors = array();
$zendLoaderPresent = false;
try {
$zendLoaderPresent = #fopen('Zend/Loader.php', 'r', true);
} catch (Exception $e) {
$zendFrameworkInstallationErrors[] = 'Exception thrown trying to ' .
'access Zend/Loader.php using \'use_include_path\' = true ' .
'Make sure you include the Zend Framework in your ' .
'include_path which currently contains: "' .
ini_get('include_path') . '"';
}
if ($zendLoaderPresent) {
#fclose($zendLoaderPresent);
require_once('Zend/Loader.php');
require_once('Zend/Version.php');
Zend_Loader::loadClass('Zend_Gdata_YouTube');
Zend_Loader::loadClass('Zend_Gdata_YouTube_VideoEntry');
$yt = new Zend_Gdata_YouTube();
$videoEntry = $yt->newVideoEntry();
if (!method_exists($videoEntry, 'setVideoTitle')) {
$zendFrameworkMessage = 'Your version of the ' .
'Zend Framework ' . Zend_Version::VERSION . ' is too old' .
' to run the YouTube demo application and does not' .
' contain the new helper methods. Please check out a' .
' newer version from Zend\'s repository: ' .
checkAndAddHTMLLink(self::ZEND_SUBVERSION_URI);
$zendFrameworkInstallationErrors[] = $zendFrameworkMessage;
}
} else {
if (count($zendFrameworkInstallationErrors) < 1) {
$zendFrameworkInstallationErrors[] = 'Exception thrown trying' .
' to access Zend/Loader.php using \'use_include_path\' =' .
' true. Make sure you include Zend Framework in your' .
' include_path which currently contains: ' .
ini_get('include_path');
}
}
$this->_allErrors[self::ZEND_GDATA_INSTALL_ERRORS]['tested'] = true;
if (count($zendFrameworkInstallationErrors) > 0) {
$this->_allErrors[self::ZEND_GDATA_INSTALL_ERRORS]['errors'] =
$zendFrameworkInstallationErrors;
return false;
}
return true;
}
/**
* Create HTML link from an input string if not in CLI mode.
*
* #param string The error message to be converted to a link.
* #return string Either the original error message or an HTML version.
*/
private function checkAndAddHTMLLink($inputString) {
if (!$this->runningInCLIMode()) {
return $this->makeHTMLLink($inputString);
} else {
return $inputString;
}
}
/**
* Create an HTML link from a string.
*
* #param string The string to be made into link text and anchor target.
* #return string HTML link.
*/
private function makeHTMLLink($inputString)
{
return '<a href="'. $inputString . '" target="_blank">' .
$inputString . '</a>';
}
/**
* Validate that SSL Capabilities are available.
*
* #return boolean False if there were errors.
*/
private function testSSLCapabilities()
{
$sslCapabilitiesErrors = array();
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Http_Client');
$httpClient = new Zend_Http_Client(
'https://www.google.com/accounts/AuthSubRequest');
$response = $httpClient->request();
$this->_allErrors[self::SSL_CAPABILITIES_ERRORS]['tested'] = true;
if ($response->isError()) {
$sslCapabilitiesErrors[] = 'Response from trying to access' .
' \'https://www.google.com/accounts/AuthSubRequest\' ' .
$response->getStatus() . ' - ' . $response->getMessage();
}
if (count($sslCapabilitiesErrors) > 0) {
$this->_allErrors[self::SSL_CAPABILITIES_ERRORS]['errors'] =
$sslCapabilitiesErrors;
return false;
}
return true;
}
/**
* Validate that we can connect to the YouTube API.
*
* #return boolean False if there were errors.
*/
private function validateYouTubeAPIConnectivity()
{
$connectivityErrors = array();
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata_YouTube');
$yt = new Zend_Gdata_YouTube();
$topRatedFeed = $yt->getTopRatedVideoFeed();
if ($topRatedFeed instanceof Zend_Gdata_YouTube_VideoFeed) {
if ($topRatedFeed->getTotalResults()->getText() < 1) {
$connectivityErrors[] = 'There was less than 1 video entry' .
' in the \'Top Rated Video Feed\'';
}
} else {
$connectivityErrors[] = 'The call to \'getTopRatedVideoFeed()\' ' .
'did not result in a Zend_Gdata_YouTube_VideoFeed object';
}
$this->_allErrors[self::YOUTUBE_API_CONNECTIVITY_ERRORS]['tested'] =
true;
if (count($connectivityErrors) > 0) {
$this->_allErrors[self::YOUTUBE_API_CONNECTIVITY_ERRORS]['tested'] =
$connectivityErrors;
return false;
}
return true;
}
/**
* Dispatch a call to outputResultsInHTML or outputResultsInText pending
* the current SAPI mode.
*
* #return void
*/
public function outputResults()
{
if ($this->_sapiModeCLI) {
print $this->getResultsInText();
} else {
print $this->getResultsInHTML();
}
}
/**
* Return a string representing the results of the verifications.
*
* #return string A string representing the results.
*/
private function getResultsInText()
{
$output = "== Ran PHP Installation Checker using CLI ==\n";
$error_count = 0;
foreach($this->_allErrors as $key => $value) {
$output .= $key . ' -- ';
if (($value['tested'] == true) && (count($value['errors']) == 0)) {
$output .= "No errors found\n";
} elseif ($value['tested'] == true) {
$output .= "Tested\n";
$error_count = 0;
foreach ($value['errors'] as $error) {
$output .= "Error number: " . $error_count . "\n--" .
$error . "\n";
}
} else {
$output .= "Not tested\n";
}
$error_count++;
}
return $output;
}
/**
* Return an HTML table representing the results of the verifications.
*
* #return string An HTML string representing the results.
*/
private function getResultsInHTML()
{
$html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " .
"\"http://www.w3.org/TR/html4/strict.dtd\">\n".
"<html><head>\n<title>PHP Installation Checker</title>\n" .
"<style type=\"text/css\">\n" .
self::CSS_WARNING . "\n" .
self::CSS_SUCCESS . "\n" .
self::CSS_ERROR . "\n" .
"</style></head>\n" .
"<body>\n<table class=\"verification_table\">" .
"<caption>Ran PHP Installation Checker on " .
gmdate('c') . "</caption>\n";
$error_count = 0;
foreach($this->_allErrors as $key => $value) {
$html .= "<tr><td class=\"verification_type\">" . $key . "</td>";
if (($value['tested'] == true) && (count($value['errors']) == 0)) {
$html .= "<td class=\"success\">Tested</td></tr>\n" .
"<tr><td colspan=\"2\">No errors found</td></tr>\n";
} elseif ($value['tested'] == true) {
$html .= "<td class=\"warning\">Tested</td></tr>\n";
$error_count = 0;
foreach ($value['errors'] as $error) {
$html .= "<tr><td class=\"error\">" . $error_count . "</td>" .
"<td class=\"error\">" . $error . "</td></tr>\n";
}
} else {
$html .= "<td class=\"warning\">Not tested</td></tr>\n";
}
$error_count++;
}
$html .= "</body></html>";
return $html;
}
}
$installationChecker = new InstallationChecker();
Go to the above link to see the results. Specifically, I'm interested in this error message:
Exception thrown trying to access Zend/Loader.php using 'use_include_path' = true.
Make sure you include Zend Framework in your include_path which currently contains:
.:/usr/lib/php:/usr/local/lib/php
Here is the link to my php info printout: http://www.jarodmorris.com/cms/phpinfo.php
I've obtained the Zend Framework from the zend website and uploaded it to a /zend folder on my website. For whatever reason, my host won't give me access to the php.ini file. I have purchased a reseller account with my host.
I'm doing all of this because I want to integrate Google Calendar API into an application I'm building on one of my websites. Maybe there is a simpler solution to my ultimate goal, but I don't see it.
Thanks for pointing me in the right direction.
EDITED TO SHOW CURRENT PHP CODE WHERE INCLUDE IS CONCERNED:
set_include_path(get_include_path() . PATH_SEPARATOR . "/home/jarodmo/public_html/zend/library");
echo get_include_path();
echo "<br>";
///home/jarodmo/public_html/zend/library/Zend
Add the dir to your include path:
set_include_path(
get_include_path()
. PATH_SEPARATOR
. '/whatever/your/zend/dir/is');
A default error logging using vqmod is as follows:
---------- Date: 2012-10-09 19:46:06 ~ IP : 127.0.0.1 ----------
REQUEST URI : /oc/
MOD DETAILS:
modFile : C:\wamp\www\oc\vqmod\xml\templace.xml
id : Template
version : 1.5.2 - 1.5.2.1
vqmver : 1.0.8
author : templace.com
SEARCH NOT FOUND (ABORTING MOD): require_once(foo . 'library/template.php');
----------------------------------------------------------------------
Example vqmod causing the error
<modification>
<id>Templace</id>
<version>1.5.2 - 1.5.2.1</version>
<author>templace.com</author>
<vqmver>1.0.8</vqmver>
<file name="system/startup.php">
<operation>
<search position="before"><![CDATA[
require_once(foo . 'library/template.php');
]]></search>
<add><![CDATA[
require_once(DIR_SYSTEM . 'library/templace.php');
]]></add>
</operation>
</file>
</modification>
To resole this issue I would have to open the vqmod file templace.xml and search for the file[name] this error is referring too.
QUESTION: How could I add the parent file[name] the actual error is referring too?
E.g adding: "system/startup.php" to the error message to make it easier to debug.
vqmod.php
/**
* VQMod
* #description Main Object used
*/
final class VQMod {
private $_vqversion = '2.1.7';
private $_modFileList = array();
private $_mods = array();
private $_filesModded = array();
private $_cwd = '';
private $_doNotMod = array();
private $_virtualMode = true;
public $useCache = false;
public $logFilePath = 'vqmod/vqmod.log';
public $vqCachePath = 'vqmod/vqcache/';
public $protectedFilelist = 'vqmod/vqprotect.txt';
public $logging = true;
public $cacheTime = 5; // local=5secs live=60secs
public $log;
/**
* VQMod::__construct()
*
* #param bool $path File path to use
* #param bool $logging Enable/disabled logging
* #return null
* #description Startup of VQMod
*/
public function __construct($path = false, $logging = true) {
if(!class_exists('DOMDocument')) {
die('ERROR - YOU NEED DOMDocument INSTALLED TO USE VQMod');
}
if(!$path){
$path = dirname(dirname(__FILE__));
}
$this->_setCwd($path);
$this->logging = (bool) $logging;
$this->log = new VQModLog($this);
$this->_getMods();
$this->_loadProtected();
}
/**
* VQMod::modCheck()
*
* #param string $sourceFile path for file
* #return string
* #description Checks if a file has modifications and applies them, returning cache files or the file name
*/
public function modCheck($sourceFile) {
if(!preg_match('%^([a-z]:)?[\\\\/]%i', $sourceFile)) {
$sourcePath = $this->path($sourceFile);
} else {
$sourcePath = realpath($sourceFile);
}
if(!$sourcePath || is_dir($sourcePath) || in_array($sourcePath, $this->_doNotMod)) {
return $sourceFile;
}
$stripped_filename = preg_replace('~^' . preg_quote($this->getCwd(), '~') . '~', '', $sourcePath);
$cacheFile = $this->_cacheName($stripped_filename);
if($this->useCache && file_exists($cacheFile)) {
//return $cacheFile; // useCache being Deprecated in favor of cacheTime
}
if(isset($this->_filesModded[$sourcePath])) {
return $this->_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile;
}
$changed = false;
$fileHash = sha1_file($sourcePath);
$fileData = file_get_contents($sourcePath);
foreach($this->_mods as $modObject) {
foreach($modObject->mods as $path => $mods) {
if($this->_checkMatch($path, $sourcePath)) {
$modObject->applyMod($mods, $fileData);
}
}
}
// START QPHORIA CACHELOCK CODE
//
if (sha1($fileData) != $fileHash) {
$writePath = $cacheFile;
$cacheLock = false;
if(file_exists($writePath) && ((filemtime($writePath) + (float)$this->cacheTime) >= time())) {
$cacheLock = true;
$changed = true;
}
if(!$cacheLock && (!file_exists($writePath) || is_writable($writePath))) {
file_put_contents($writePath, $fileData);
$changed = true;
} else {
//file_put_contents('./cachelock.txt', "$writePath \r\n", FILE_APPEND); // debugging only.
}
//file_put_contents('./cachetotal.txt', "$writePath \r\n", FILE_APPEND);
} // END QPHORIA CACHELOCK CODE
/* Original Code
if(sha1($fileData) != $fileHash) {
$writePath = $this->_virtualMode ? $cacheFile : $sourcePath;
if(!file_exists($writePath) || is_writable($writePath)) {
file_put_contents($writePath, $fileData);
$changed = true;
}
}*/
$this->_filesModded[$sourcePath] = array('cached' => $changed);
return $changed ? $writePath : $sourcePath;
}
/**
* VQMod::path()
*
* #param string $path File path
* #param bool $skip_real If true path is full not relative
* #return bool, string
* #description Returns the full true path of a file if it exists, otherwise false
*/
public function path($path, $skip_real = false) {
$tmp = $this->_cwd . $path;
$realpath = $skip_real ? $tmp : realpath($tmp);
if(!$realpath) {
return false;
}
if(is_dir($realpath)) {
$realpath = rtrim($realpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
return $realpath;
}
/**
* VQMod::getCwd()
*
* #return string
* #description Returns current working directory
*/
public function getCwd() {
return $this->_cwd;
}
/**
* VQMod::_getMods()
*
* #return null
* #description Gets list of XML files in vqmod xml folder for processing
*/
private function _getMods() {
$this->_modFileList = glob($this->path('vqmod/xml/') . '*.xml');
if($this->_modFileList) {
$this->_parseMods();
} else {
$this->log->write('NO MODS IN USE');
}
}
/**
* VQMod::_parseMods()
*
* #return null
* #description Loops through xml files and attempts to load them as VQModObject's
*/
private function _parseMods() {
$dom = new DOMDocument('1.0', 'UTF-8');
foreach($this->_modFileList as $modFileKey => $modFile) {
if(file_exists($modFile)) {
if(#$dom->load($modFile)) {
$mod = $dom->getElementsByTagName('modification')->item(0);
$this->_mods[] = new VQModObject($mod, $modFile, $this);
} else {
$this->log->write('DOM UNABLE TO LOAD: ' . $modFile);
}
} else {
$this->log->write('FILE NOT FOUND: ' . $modFile);
}
}
}
/**
* VQMod::_loadProtected()
*
* #return null
* #description Loads protected list and adds them to _doNotMod array
*/
private function _loadProtected() {
$file = $this->path($this->protectedFilelist);
if($file && is_file($file)) {
$protected = file_get_contents($file);
if(!empty($protected)) {
$protected = preg_replace('~\r?\n~', "\n", $protected);
$paths = explode("\n", $protected);
foreach($paths as $path) {
$fullPath = $this->path($path);
if($fullPath && !in_array($fullPath, $this->_doNotMod)) {
$this->_doNotMod[] = $fullPath;
}
}
}
}
}
/**
* VQMod::_cacheName()
*
* #param string $file Filename to be converted to cache filename
* #return string
* #description Returns cache file name for a path
*/
private function _cacheName($file) {
return $this->path($this->vqCachePath) . 'vq2-' . preg_replace('~[/\\\\]+~', '_', $file);
}
/**
* VQMod::_setCwd()
*
* #param string $path Path to be used as current working directory
* #return null
* #description Sets the current working directory variable
*/
private function _setCwd($path) {
$realpath = realpath($path);
if(!$realpath) {
die('COULDNT RESOLVE CWD REALPATH');
}
$this->_cwd = rtrim($realpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
/**
* VQMod::_checkMatch()
*
* #param string $modFilePath Modification path from a <file> node
* #param string $checkFilePath File path
* #return bool
* #description Checks a modification path against a file path
*/
private function _checkMatch($modFilePath, $checkFilePath) {
$modFilePath = str_replace('\\', '/', $modFilePath);
$checkFilePath = str_replace('\\', '/', $checkFilePath);
$modFilePath = preg_replace('/([^*]+)/e', 'preg_quote("$1", "~")', $modFilePath);
$modFilePath = str_replace('*', '[^/]*', $modFilePath);
$return = (bool) preg_match('~^' . $modFilePath . '$~', $checkFilePath);
return $return;
}
}
/**
* VQModLog
* #description Object to log information to a file
*/
class VQModLog {
private $_sep;
private $_vqmod;
private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
private $_logs = array();
/**
* VQModLog::__construct()
*
* #param VQMod $vqmod VQMod main class as reference
* #return null
* #description Object instantiation method
*/
public function __construct(VQMod $vqmod) {
$this->_vqmod = $vqmod;
$this->_sep = str_repeat('-', 70);
}
/**
* VQModLog::__destruct()
*
* #return null
* #description Logs any messages to the log file just before object is destroyed
*/
public function __destruct() {
if(empty($this->_logs) || $this->_vqmod->logging == false) {
return;
}
$txt = array();
$txt[] = str_repeat('-', 10) . ' Date: ' . date('Y-m-d H:i:s') . ' ~ IP : ' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'N/A') . ' ' . str_repeat('-', 10);
$txt[] = 'REQUEST URI : ' . $_SERVER['REQUEST_URI'];
foreach($this->_logs as $count => $log) {
if($log['obj']) {
$vars = get_object_vars($log['obj']);
$txt[] = 'MOD DETAILS:';
foreach($vars as $k => $v) {
if(is_string($v)) {
$txt[] = ' ' . str_pad($k, 10, ' ', STR_PAD_RIGHT) . ': ' . $v;
}
}
}
foreach($log['log'] as $msg) {
$txt[] = $msg;
}
if ($count > count($this->_logs)-1) {
$txt[] = '';
}
}
$txt[] = $this->_sep;
$txt[] = str_repeat(PHP_EOL, 2);
$logPath = $this->_vqmod->path($this->_vqmod->logFilePath, true);
if(!file_exists($logPath)) {
$res = file_put_contents($logPath, '');
if($res === false) {
die('COULD NOT WRITE TO LOG FILE');
}
}
file_put_contents($logPath, implode(PHP_EOL, $txt), FILE_APPEND);
}
/**
* VQModLog::write()
*
* #param string $data Text to be added to log file
* #param VQModObject $obj Modification the error belongs to
* #return null
* #description Adds error to log object ready to be output
*/
public function write($data, VQModObject $obj = NULL) {
if($obj) {
$hash = sha1($obj->id);
} else {
$hash = $this->_defhash;
}
if(empty($this->_logs[$hash])) {
$this->_logs[$hash] = array(
'obj' => $obj,
'log' => array()
);
}
$this->_logs[$hash]['log'][] = $data;
}
}
/**
* VQModObject
* #description Object for the <modification> that orchestrates each applied modification
*/
class VQModObject {
public $modFile = '';
public $id = '';
public $version = '';
public $vqmver = '';
public $author = '';
public $mods = array();
private $_vqmod;
private $_skip = false;
/**
* VQModObject::__construct()
*
* #param DOMNode $node <modification> node
* #param string $modFile File modification is from
* #param VQMod $vqmod VQMod object as reference
* #return null
* #description Loads modification meta information
*/
public function __construct(DOMNode $node, $modFile, VQmod $vqmod) {
if($node->hasChildNodes()) {
foreach($node->childNodes as $child) {
$name = (string) $child->nodeName;
if(isset($this->$name)) {
$this->$name = (string) $child->nodeValue;
}
}
}
$this->modFile = $modFile;
$this->_vqmod = $vqmod;
$this->_parseMods($node);
}
/**
* VQModObject::skip()
*
* #return bool
* #description Returns the skip status of a modification
*/
public function skip() {
return $this->_skip;
}
/**
* VQModObject::applyMod()
*
* #param array $mods Array of search add nodes
* #param string $data File contents to be altered
* #return null
* #description Applies all modifications to the text data
*/
public function applyMod($mods, &$data) {
if($this->_skip) return;
$tmp = $data;
foreach($mods as $mod) {
$indexCount = 0;
$tmp = $this->_explodeData($tmp);
$lineMax = count($tmp) - 1;
switch($mod['search']->position) {
case 'top':
$tmp[$mod['search']->offset] = $mod['add']->getContent() . $tmp[$mod['search']->offset];
break;
case 'bottom':
$offset = $lineMax - $mod['search']->offset;
if($offset < 0){
$tmp[-1] = $mod['add']->getContent();
} else {
$tmp[$offset] .= $mod['add']->getContent();
}
break;
case 'all':
$tmp = array($mod['add']->getContent());
break;
default:
$changed = false;
foreach($tmp as $lineNum => $line) {
if($mod['search']->regex == 'true') {
$pos = #preg_match($mod['search']->getContent(), $line);
if($pos === false) {
if($mod['error'] == 'log' || $mod['error'] == 'abort' ) {
$this->_vqmod->log->write('INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this);
}
continue 2;
} elseif($pos == 0) {
$pos = false;
}
} else {
$pos = strpos($line, $mod['search']->getContent());
}
if($pos !== false) {
$indexCount++;
$changed = true;
if(!$mod['search']->indexes() || ($mod['search']->indexes() && in_array($indexCount, $mod['search']->indexes()))) {
switch($mod['search']->position) {
case 'before':
$offset = ($lineNum - $mod['search']->offset < 0) ? -1 : $lineNum - $mod['search']->offset;
$tmp[$offset] = empty($tmp[$offset]) ? $mod['add']->getContent() : $mod['add']->getContent() . "\n" . $tmp[$offset];
break;
case 'after':
$offset = ($lineNum + $mod['search']->offset > $lineMax) ? $lineMax : $lineNum + $mod['search']->offset;
$tmp[$offset] = $tmp[$offset] . "\n" . $mod['add']->getContent();
break;
default:
if(!empty($mod['search']->offset)) {
for($i = 1; $i <= $mod['search']->offset; $i++) {
if(isset($tmp[$lineNum + $i])) {
$tmp[$lineNum + $i] = '';
}
}
}
if($mod['search']->regex == 'true') {
$tmp[$lineNum] = preg_replace($mod['search']->getContent(), $mod['add']->getContent(), $line);
} else {
$tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent(), $line);
}
break;
}
}
}
}
if(!$changed) {
$skip = ($mod['error'] == 'skip' || $mod['error'] == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)';
if($mod['error'] == 'log' || $mod['error'] == 'abort') {
$this->_vqmod->log->write('SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this);
}
if($mod['error'] == 'abort') {
$this->_skip = true;
return;
}
}
break;
}
ksort($tmp);
$tmp = $this->_implodeData($tmp);
}
$data = $tmp;
}
/**
* VQModObject::_parseMods()
*
* #param DOMNode $node <modification> node to be parsed
* #return null
* #description Parses modifications in preparation for the applyMod method to work
*/
private function _parseMods(DOMNode $node){
$files = $node->getElementsByTagName('file');
foreach($files as $file) {
$fileToMod = $file->getAttribute('name');
$error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log';
$fullPath = $this->_vqmod->path($fileToMod);
if(!$fullPath){
if(strpos($fileToMod, '*') !== false) {
$fullPath = $this->_vqmod->getCwd() . $fileToMod;
} else {
if ($error == 'log' || $error == 'abort') {
$skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)';
$this->_vqmod->log->write('Could not resolve path for [' . $fileToMod . ']' . $skip, $this);
}
if ($error == 'log' || $error == 'skip') {
continue;
} elseif ($error == 'abort') {
return false;
}
}
}
$operations = $file->getElementsByTagName('operation');
foreach($operations as $operation) {
$error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort';
$this->mods[$fullPath][] = array(
'search' => new VQSearchNode($operation->getElementsByTagName('search')->item(0)),
'add' => new VQAddNode($operation->getElementsByTagName('add')->item(0)),
'error' => $error
);
}
}
}
/**
* VQModObject::_explodeData()
*
* #param string $data File contents
* #return string
* #description Splits a file into an array of individual lines
*/
private function _explodeData($data) {
return explode("\n", $data);
}
/**
* VQModObject::_implodeData()
*
* #param array $data Array of lines
* #return string
* #description Joins an array of lines back into a text file
*/
private function _implodeData($data) {
return implode("\n", $data);
}
}
/**
* VQNode
* #description Basic node object blueprint
*/
class VQNode {
public $trim = 'false';
private $_content = '';
/**
* VQNode::__construct()
*
* #param DOMNode $node Search/add node
* #return null
* #description Parses the node attributes and sets the node property
*/
public function __construct(DOMNode $node) {
$this->_content = $node->nodeValue;
if($node->hasAttributes()) {
foreach($node->attributes as $attr) {
$name = $attr->nodeName;
if(isset($this->$name)) {
$this->$name = $attr->nodeValue;
}
}
}
}
/**
* VQNode::getContent()
*
* #return string
* #description Returns the content, trimmed if applicable
*/
public function getContent() {
$content = ($this->trim == 'true') ? trim($this->_content) : $this->_content;
return $content;
}
}
/**
* VQSearchNode
* #description Object for the <search> xml tags
*/
class VQSearchNode extends VQNode {
public $position = 'replace';
public $offset = 0;
public $index = 'false';
public $regex = 'false';
public $trim = 'true';
/**
* VQSearchNode::indexes()
*
* #return bool, array
* #description Returns the index values to use the search on, or false if none
*/
public function indexes() {
if($this->index == 'false') {
return false;
}
$tmp = explode(',', $this->index);
foreach($tmp as $k => $v) {
if(!is_int($v)) {
unset($k);
}
}
$tmp = array_unique($tmp);
return empty($tmp) ? false : $tmp;
}
}
/**
* VQAddNode
* #description Object for the <add> xml tags
*/
class VQAddNode extends VQNode {
}
Also couple of other ideas to make debugging even easier:
List any other vqmod files which have previously edited this same file.
This is another common issue where I find when two extensions are editing the same file and the latter is causing the error but it would be useful to know about any other vqmods editing the same file. Yes I suppose I could add error="skip" to everything but dont think this is the best approach to just hide all of the errors, the user should be made aware there is an error...
"Suggested Fix", maybe some smart way you can test what type of error it is.
Contradict what I said above but even at its most basic form you could suggest hiding the error if its not essential. So that anybody can read it and understand how it fix it.
E.g
OPEN: vqmod/xml/templace.xml (line:23)
FIND: <operation>
REPLACE <operation error="skip">
Adding the line number in the XML file the error is coming from. It would be lovely not having to search all of the time and could quickly go to the line number in the vqmod
The issue for the file being edited is certainly one that is way overdue and one I plan on adding in the next release of vQmod. As for the other suggestions
Interesting idea, and one that could certainly be considered. The only problem I see with this is that it would possibly make some log files enormous
This is going to be next to impossible to incorporate
This is impossible without some pretty expensive runtime. The error doesn't lie in the XML as such, so would require re-opening the xml that's been parsed, searching for the line in question line by line and then reporting that. it sounds simple, but you have to remember that xml's can have the same search parameter for multiple operations - so in that situation you'd be no better off than searching the file yourself