Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   1  <?php
   2  /**
   3   * Library for CSV serialization.
   4   *
   5   * This is used by the csv/proxy driver and is the CacheExecute()
   6   * serialization format.
   7   *
   8   * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
   9   *
  10   * @package ADOdb
  11   * @link https://adodb.org Project's web site and documentation
  12   * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
  13   *
  14   * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
  15   * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
  16   * any later version. This means you can use it in proprietary products.
  17   * See the LICENSE.md file distributed with this source code for details.
  18   * @license BSD-3-Clause
  19   * @license LGPL-2.1-or-later
  20   *
  21   * @copyright 2000-2013 John Lim
  22   * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
  23   */
  24  
  25  // security - hide paths
  26  if (!defined('ADODB_DIR')) die();
  27  
  28  global $ADODB_INCLUDED_CSV;
  29  $ADODB_INCLUDED_CSV = 1;
  30  
  31  	 /**
  32   	  * Convert a recordset into special format
  33  	  *
  34  	  * @param ADORecordSet  $rs the recordset
  35  	  * @param ADOConnection $conn
  36  	  * @param string        $sql
  37  	  *
  38  	  * @return string the CSV formatted data
  39  	  */
  40  	function _rs2serialize(&$rs,$conn=false,$sql='')
  41  	 {
  42  	 	 $max = ($rs) ? $rs->FieldCount() : 0;
  43  
  44  	 	 if ($sql) $sql = urlencode($sql);
  45  	 	 // metadata setup
  46  
  47  	 	 if ($max <= 0 || $rs->dataProvider == 'empty') { // is insert/update/delete
  48  	 	 	 if (is_object($conn)) {
  49  	 	 	 	 $sql .= ','.$conn->Affected_Rows();
  50  	 	 	 	 $sql .= ','.$conn->Insert_ID();
  51  	 	 	 } else
  52  	 	 	 	 $sql .= ',,';
  53  
  54  	 	 	 $text = "====-1,0,$sql\n";
  55  	 	 	 return $text;
  56  	 	 }
  57  	 	 $tt = ($rs->timeCreated) ? $rs->timeCreated : time();
  58  
  59  	 	 ## changed format from ====0 to ====1
  60  	 	 $line = "====1,$tt,$sql\n";
  61  
  62  	 	 if ($rs->databaseType == 'array') {
  63  	 	 	 $rows = $rs->_array;
  64  	 	 } else {
  65  	 	 	 $rows = array();
  66  	 	 	 while (!$rs->EOF) {
  67  	 	 	 	 $rows[] = $rs->fields;
  68  	 	 	 	 $rs->MoveNext();
  69  	 	 	 }
  70  	 	 }
  71  
  72  	 	 for($i=0; $i < $max; $i++) {
  73  	 	 	 $o = $rs->FetchField($i);
  74  	 	 	 $flds[] = $o;
  75  	 	 }
  76  
  77  	 	 $savefetch = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
  78  	 	 $class = $rs->connection->arrayClass;
  79  	 	 $rs2 = new $class(ADORecordSet::DUMMY_QUERY_ID);
  80  	 	 $rs2->timeCreated = $rs->timeCreated; # memcache fix
  81  	 	 $rs2->sql = $rs->sql;
  82  	 	 $rs2->oldProvider = $rs->dataProvider;
  83  	 	 $rs2->InitArrayFields($rows,$flds);
  84  	 	 $rs2->fetchMode = $savefetch;
  85  	 	 return $line.serialize($rs2);
  86  	 }
  87  
  88  	 /**
  89  	  * Open CSV file and convert it into Data.
  90  	  *
  91  	  * @param string $url     file/ftp/http url
  92  	  * @param string &$err    returns the error message
  93  	  * @param int $timeout    dispose if recordset has been alive for $timeout secs
  94  	  * @param string $rsclass RecordSet class to return
  95  	  *
  96  	  * @return ADORecordSet|false recordset, or false if error occurred.
  97  	  *                            If no error occurred in sql INSERT/UPDATE/DELETE,
  98  	  *                            empty recordset is returned.
  99  	  */
 100  	function csv2rs($url, &$err, $timeout=0, $rsclass='ADORecordSet_array')
 101  	 {
 102  	 	 $false = false;
 103  	 	 $err = false;
 104  	 	 $fp = @fopen($url,'rb');
 105  	 	 if (!$fp) {
 106  	 	 	 $err = $url.' file/URL not found';
 107  	 	 	 return $false;
 108  	 	 }
 109  	 	 @flock($fp, LOCK_SH);
 110  	 	 $arr = array();
 111  	 	 $ttl = 0;
 112  
 113  	 	 if ($meta = fgetcsv($fp, 32000, ",")) {
 114  	 	 	 // check if error message
 115  	 	 	 if (strncmp($meta[0],'****',4) === 0) {
 116  	 	 	 	 $err = trim(substr($meta[0],4,1024));
 117  	 	 	 	 fclose($fp);
 118  	 	 	 	 return $false;
 119  	 	 	 }
 120  	 	 	 // check for meta data
 121  	 	 	 // $meta[0] is -1 means return an empty recordset
 122  	 	 	 // $meta[1] contains a time
 123  
 124  	 	 	 if (strncmp($meta[0], '====',4) === 0) {
 125  
 126  	 	 	 	 if ($meta[0] == "====-1") {
 127  	 	 	 	 	 if (sizeof($meta) < 5) {
 128  	 	 	 	 	 	 $err = "Corrupt first line for format -1";
 129  	 	 	 	 	 	 fclose($fp);
 130  	 	 	 	 	 	 return $false;
 131  	 	 	 	 	 }
 132  	 	 	 	 	 fclose($fp);
 133  
 134  	 	 	 	 	 if ($timeout > 0) {
 135  	 	 	 	 	 	 $err = " Illegal Timeout $timeout ";
 136  	 	 	 	 	 	 return $false;
 137  	 	 	 	 	 }
 138  
 139  	 	 	 	 	 $rs = new $rsclass($val=true);
 140  	 	 	 	 	 $rs->fields = array();
 141  	 	 	 	 	 $rs->timeCreated = $meta[1];
 142  	 	 	 	 	 $rs->EOF = true;
 143  	 	 	 	 	 $rs->_numOfFields = 0;
 144  	 	 	 	 	 $rs->sql = urldecode($meta[2]);
 145  	 	 	 	 	 $rs->affectedrows = (integer)$meta[3];
 146  	 	 	 	 	 $rs->insertid = $meta[4];
 147  	 	 	 	 	 return $rs;
 148  	 	 	 	 }
 149  	 	 	 # Under high volume loads, we want only 1 thread/process to _write_file
 150  	 	 	 # so that we don't have 50 processes queueing to write the same data.
 151  	 	 	 # We use probabilistic timeout, ahead of time.
 152  	 	 	 #
 153  	 	 	 # -4 sec before timeout, give processes 1/32 chance of timing out
 154  	 	 	 # -2 sec before timeout, give processes 1/16 chance of timing out
 155  	 	 	 # -1 sec after timeout give processes 1/4 chance of timing out
 156  	 	 	 # +0 sec after timeout, give processes 100% chance of timing out
 157  	 	 	 	 if (sizeof($meta) > 1) {
 158  	 	 	 	 	 if($timeout >0){
 159  	 	 	 	 	 	 $tdiff = (integer)( $meta[1]+$timeout - time());
 160  	 	 	 	 	 	 if ($tdiff <= 2) {
 161  	 	 	 	 	 	 	 switch($tdiff) {
 162  	 	 	 	 	 	 	 case 4:
 163  	 	 	 	 	 	 	 case 3:
 164  	 	 	 	 	 	 	 	 if ((rand() & 31) == 0) {
 165  	 	 	 	 	 	 	 	 	 fclose($fp);
 166  	 	 	 	 	 	 	 	 	 $err = "Timeout 3";
 167  	 	 	 	 	 	 	 	 	 return $false;
 168  	 	 	 	 	 	 	 	 }
 169  	 	 	 	 	 	 	 	 break;
 170  	 	 	 	 	 	 	 case 2:
 171  	 	 	 	 	 	 	 	 if ((rand() & 15) == 0) {
 172  	 	 	 	 	 	 	 	 	 fclose($fp);
 173  	 	 	 	 	 	 	 	 	 $err = "Timeout 2";
 174  	 	 	 	 	 	 	 	 	 return $false;
 175  	 	 	 	 	 	 	 	 }
 176  	 	 	 	 	 	 	 	 break;
 177  	 	 	 	 	 	 	 case 1:
 178  	 	 	 	 	 	 	 	 if ((rand() & 3) == 0) {
 179  	 	 	 	 	 	 	 	 	 fclose($fp);
 180  	 	 	 	 	 	 	 	 	 $err = "Timeout 1";
 181  	 	 	 	 	 	 	 	 	 return $false;
 182  	 	 	 	 	 	 	 	 }
 183  	 	 	 	 	 	 	 	 break;
 184  	 	 	 	 	 	 	 default:
 185  	 	 	 	 	 	 	 	 fclose($fp);
 186  	 	 	 	 	 	 	 	 $err = "Timeout 0";
 187  	 	 	 	 	 	 	 	 return $false;
 188  	 	 	 	 	 	 	 } // switch
 189  
 190  	 	 	 	 	 	 } // if check flush cache
 191  	 	 	 	 	 }// (timeout>0)
 192  	 	 	 	 	 $ttl = $meta[1];
 193  	 	 	 	 }
 194  	 	 	 	 //================================================
 195  	 	 	 	 // new cache format - use serialize extensively...
 196  	 	 	 	 if ($meta[0] === '====1') {
 197  	 	 	 	 	 // slurp in the data
 198  	 	 	 	 	 $MAXSIZE = 128000;
 199  
 200  	 	 	 	 	 $text = fread($fp,$MAXSIZE);
 201  	 	 	 	 	 if (strlen($text)) {
 202  	 	 	 	 	 	 while ($txt = fread($fp,$MAXSIZE)) {
 203  	 	 	 	 	 	 	 $text .= $txt;
 204  	 	 	 	 	 	 }
 205  	 	 	 	 	 }
 206  	 	 	 	 	 fclose($fp);
 207  	 	 	 	 	 $rs = unserialize($text);
 208  	 	 	 	 	 if (is_object($rs)) $rs->timeCreated = $ttl;
 209  	 	 	 	 	 else {
 210  	 	 	 	 	 	 $err = "Unable to unserialize recordset";
 211  	 	 	 	 	 	 //echo htmlspecialchars($text),' !--END--!<p>';
 212  	 	 	 	 	 }
 213  	 	 	 	 	 return $rs;
 214  	 	 	 	 }
 215  
 216  	 	 	 	 $meta = false;
 217  	 	 	 	 $meta = fgetcsv($fp, 32000, ",");
 218  	 	 	 	 if (!$meta) {
 219  	 	 	 	 	 fclose($fp);
 220  	 	 	 	 	 $err = "Unexpected EOF 1";
 221  	 	 	 	 	 return $false;
 222  	 	 	 	 }
 223  	 	 	 }
 224  
 225  	 	 	 // Get Column definitions
 226  	 	 	 $flds = array();
 227  	 	 	 foreach($meta as $o) {
 228  	 	 	 	 $o2 = explode(':',$o);
 229  	 	 	 	 if (sizeof($o2)!=3) {
 230  	 	 	 	 	 $arr[] = $meta;
 231  	 	 	 	 	 $flds = false;
 232  	 	 	 	 	 break;
 233  	 	 	 	 }
 234  	 	 	 	 $fld = new ADOFieldObject();
 235  	 	 	 	 $fld->name = urldecode($o2[0]);
 236  	 	 	 	 $fld->type = $o2[1];
 237  	 	 	 	 $fld->max_length = $o2[2];
 238  	 	 	 	 $flds[] = $fld;
 239  	 	 	 }
 240  	 	 } else {
 241  	 	 	 fclose($fp);
 242  	 	 	 $err = "Recordset had unexpected EOF 2";
 243  	 	 	 return $false;
 244  	 	 }
 245  
 246  	 	 // slurp in the data
 247  	 	 $MAXSIZE = 128000;
 248  
 249  	 	 $text = '';
 250  	 	 while ($txt = fread($fp,$MAXSIZE)) {
 251  	 	 	 $text .= $txt;
 252  	 	 }
 253  
 254  	 	 fclose($fp);
 255  	 	 @$arr = unserialize($text);
 256  	 	 if (!is_array($arr)) {
 257  	 	 	 $err = "Recordset had unexpected EOF (in serialized recordset)";
 258  	 	 	 return $false;
 259  	 	 }
 260  	 	 $rs = new $rsclass();
 261  	 	 $rs->timeCreated = $ttl;
 262  	 	 $rs->InitArrayFields($arr,$flds);
 263  	 	 return $rs;
 264  	 }
 265  
 266  
 267  	 /**
 268  	 * Save a file $filename and its $contents (normally for caching) with file locking
 269  	 * Returns true if ok, false if fopen/fwrite error, 0 if rename error (eg. file is locked)
 270  	 */
 271  	function adodb_write_file($filename, $contents,$debug=false)
 272  	 {
 273  	 # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows
 274  	 # So to simulate locking, we assume that rename is an atomic operation.
 275  	 # First we delete $filename, then we create a $tempfile write to it and
 276  	 # rename to the desired $filename. If the rename works, then we successfully
 277  	 # modified the file exclusively.
 278  	 # What a stupid need - having to simulate locking.
 279  	 # Risks:
 280  	 # 1. $tempfile name is not unique -- very very low
 281  	 # 2. unlink($filename) fails -- ok, rename will fail
 282  	 # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs
 283  	 # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and  cache updated
 284  	 	 if (strncmp(PHP_OS,'WIN',3) === 0) {
 285  	 	 	 // skip the decimal place
 286  	 	 	 $mtime = substr(str_replace(' ','_',microtime()),2);
 287  	 	 	 // getmypid() actually returns 0 on Win98 - never mind!
 288  	 	 	 $tmpname = $filename.uniqid($mtime).getmypid();
 289  	 	 	 if (!($fd = @fopen($tmpname,'w'))) return false;
 290  	 	 	 if (fwrite($fd,$contents)) $ok = true;
 291  	 	 	 else $ok = false;
 292  	 	 	 fclose($fd);
 293  
 294  	 	 	 if ($ok) {
 295  	 	 	 	 @chmod($tmpname,0644);
 296  	 	 	 	 // the tricky moment
 297  	 	 	 	 @unlink($filename);
 298  	 	 	 	 if (!@rename($tmpname,$filename)) {
 299  	 	 	 	 	 @unlink($tmpname);
 300  	 	 	 	 	 $ok = 0;
 301  	 	 	 	 }
 302  	 	 	 	 if (!$ok) {
 303  	 	 	 	 	 if ($debug) ADOConnection::outp( " Rename $tmpname ".($ok? 'ok' : 'failed'));
 304  	 	 	 	 }
 305  	 	 	 }
 306  	 	 	 return $ok;
 307  	 	 }
 308  	 	 if (!($fd = @fopen($filename, 'a'))) return false;
 309  	 	 if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) {
 310  	 	 	 if (fwrite( $fd, $contents )) $ok = true;
 311  	 	 	 else $ok = false;
 312  	 	 	 fclose($fd);
 313  	 	 	 @chmod($filename,0644);
 314  	 	 }else {
 315  	 	 	 fclose($fd);
 316  	 	 	 if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename<br>\n");
 317  	 	 	 $ok = false;
 318  	 	 }
 319  
 320  	 	 return $ok;
 321  	 }