First commit
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* Modified version of KLogger 0.2.0
|
||||
*
|
||||
* @author Kenny Katzgrau <katzgrau@gmail.com>
|
||||
*
|
||||
* @package logger
|
||||
*/
|
||||
|
||||
class Logger
|
||||
{
|
||||
/**
|
||||
* Error severity, from low to high. From BSD syslog RFC, section 4.1.1
|
||||
* @link http://www.faqs.org/rfcs/rfc3164.html
|
||||
*/
|
||||
const EMERGENCY = 0; // Emergency: system is unusable
|
||||
const ALERT = 1; // Alert: action must be taken immediately
|
||||
const CRITICAL = 2; // Critical: critical conditions
|
||||
const ERROR = 3; // Error: error conditions
|
||||
const WARNING = 4; // Warning: warning conditions
|
||||
const NOTICE = 5; // Notice: normal but significant condition
|
||||
const INFO = 6; // Informational: informational messages
|
||||
const DEBUG = 7; // Debug: debug messages
|
||||
|
||||
/**
|
||||
* Custom "disable" level.
|
||||
*/
|
||||
const OFF = -1; // Log nothing at all
|
||||
|
||||
/**
|
||||
* Internal status codes.
|
||||
*/
|
||||
const STATUS_LOG_OPEN = 1;
|
||||
const STATUS_OPEN_FAILED = 2;
|
||||
const STATUS_LOG_CLOSED = 3;
|
||||
|
||||
/**
|
||||
* Disable archive purge.
|
||||
*/
|
||||
const ARCHIVE_NO_PURGE = -1;
|
||||
|
||||
/**
|
||||
* Standard messages produced by the class.
|
||||
* @var array
|
||||
*/
|
||||
private static $_messages = array(
|
||||
'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.',
|
||||
'opensuccess' => 'The log file was opened successfully.',
|
||||
'openfail' => 'The file could not be opened. Check permissions.',
|
||||
);
|
||||
|
||||
/**
|
||||
* Instance options.
|
||||
* @var array
|
||||
*/
|
||||
private $options = array(
|
||||
'directory' => null, // Log files directory
|
||||
'filename' => null, // Path to the log file
|
||||
'globPattern' => 'log_*.txt', // Pattern to select all log files with glob()
|
||||
'severity' => self::DEBUG, // Current minimum logging threshold
|
||||
'dateFormat' => 'Y-m-d G:i:s', // Date format
|
||||
'archiveDays' => self::ARCHIVE_NO_PURGE, // Number of files to keep
|
||||
);
|
||||
|
||||
/**
|
||||
* Current status of the logger.
|
||||
* @var integer
|
||||
*/
|
||||
private $_logStatus = self::STATUS_LOG_CLOSED;
|
||||
/**
|
||||
* File handle for this instance's log file.
|
||||
* @var resource
|
||||
*/
|
||||
private $_fileHandle = null;
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->options = array_merge($this->options, $options);
|
||||
|
||||
if (is_string($this->options['severity']))
|
||||
{
|
||||
$this->options['severity'] = self::codeToLevel($this->options['severity']);
|
||||
}
|
||||
|
||||
if ($this->options['severity'] === self::OFF)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->options['directory'] = rtrim($this->options['directory'], '\\/') . DIRECTORY_SEPARATOR;
|
||||
|
||||
if ($this->options['filename'] == null)
|
||||
{
|
||||
$this->options['filename'] = 'log_' . date('Y-m-d') . '.txt';
|
||||
}
|
||||
|
||||
$this->options['filePath'] = $this->options['directory'] . $this->options['filename'];
|
||||
|
||||
if ($this->options['archiveDays'] != self::ARCHIVE_NO_PURGE && rand() % 97 == 0)
|
||||
{
|
||||
$this->purge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the log file if not already oppenned
|
||||
*/
|
||||
private function open()
|
||||
{
|
||||
if ($this->status() == self::STATUS_LOG_CLOSED)
|
||||
{
|
||||
if (!file_exists($this->options['directory']))
|
||||
{
|
||||
mkgetdir($this->options['directory'], MKGETDIR_DEFAULT|MKGETDIR_PROTECT_HTACCESS);
|
||||
}
|
||||
|
||||
if (file_exists($this->options['filePath']) && !is_writable($this->options['filePath']))
|
||||
{
|
||||
$this->_logStatus = self::STATUS_OPEN_FAILED;
|
||||
throw new RuntimeException(self::$_messages['writefail']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (($this->_fileHandle = fopen($this->options['filePath'], 'a')) != false)
|
||||
{
|
||||
$this->_logStatus = self::STATUS_LOG_OPEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_logStatus = self::STATUS_OPEN_FAILED;
|
||||
throw new RuntimeException(self::$_messages['openfail']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->_fileHandle)
|
||||
{
|
||||
fclose($this->_fileHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns logger status.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return $this->_logStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns logger severity threshold.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function severity()
|
||||
{
|
||||
return $this->options['severity'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of DEBUG.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function debug($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::DEBUG, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of INFO.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function info($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::INFO, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of NOTICE.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function notice($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::NOTICE, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of WARNING.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function warn($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::WARNING, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of ERROR.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function error($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::ERROR, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of ALERT.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function alert($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::ALERT, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of CRITICAL.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function critical($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::CRITICAL, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with a severity level of EMERGENCY.
|
||||
*
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function emergency($line, $cat = null, $args = array())
|
||||
{
|
||||
$this->log(self::EMERGENCY, $line, $cat, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a $line to the log with the given severity.
|
||||
*
|
||||
* @param integer $severity
|
||||
* @param string $line
|
||||
* @param string $cat
|
||||
* @param array $args
|
||||
*/
|
||||
public function log($severity, $message, $cat = null, $args = array())
|
||||
{
|
||||
if ($this->severity() >= $severity)
|
||||
{
|
||||
if (is_array($cat))
|
||||
{
|
||||
$args = $cat;
|
||||
$cat = null;
|
||||
}
|
||||
$line = $this->formatMessage($severity, $message, $cat, $args);
|
||||
$this->write($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly writes a line to the log without adding level and time.
|
||||
*
|
||||
* @param string $line
|
||||
*/
|
||||
public function write($line)
|
||||
{
|
||||
$this->open();
|
||||
if ($this->status() == self::STATUS_LOG_OPEN)
|
||||
{
|
||||
if (fwrite($this->_fileHandle, $line) === false)
|
||||
{
|
||||
throw new RuntimeException(self::$_messages['writefail']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges files matching 'globPattern' older than 'archiveDays'.
|
||||
*/
|
||||
public function purge()
|
||||
{
|
||||
$files = glob($this->options['directory'] . $this->options['globPattern']);
|
||||
$limit = time() - $this->options['archiveDays'] * 86400;
|
||||
|
||||
foreach ($files as $file)
|
||||
{
|
||||
if (@filemtime($file) < $limit)
|
||||
{
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the message for logging.
|
||||
*
|
||||
* @param string $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return string
|
||||
*/
|
||||
private function formatMessage($level, $message, $cat, $context)
|
||||
{
|
||||
global $page;
|
||||
|
||||
if (!empty($context))
|
||||
{
|
||||
$message.= "\n" . $this->indent($this->contextToString($context));
|
||||
}
|
||||
$line = "[" . $this->getTimestamp() . '][exec='.($page['execution_uuid']??'unkonwn')."]\t[" . self::levelToCode($level) . "]\t";
|
||||
if ($cat != null)
|
||||
{
|
||||
$line.= "[" . $cat . "]\t";
|
||||
}
|
||||
return $line . $message . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted Date/Time for the log entry.
|
||||
*
|
||||
* PHP DateTime is dumb, and you have to resort to trickery to get microseconds
|
||||
* to work correctly, so here it is.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTimestamp()
|
||||
{
|
||||
$originalTime = microtime(true);
|
||||
$micro = sprintf('%06d', ($originalTime - floor($originalTime)) * 1000000);
|
||||
$date = new DateTime(date('Y-m-d H:i:s.'.$micro, intval($originalTime)));
|
||||
return $date->format($this->options['dateFormat']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the given context and converts it to a string.
|
||||
*
|
||||
* @param array $context
|
||||
* @return string
|
||||
*/
|
||||
private function contextToString($context)
|
||||
{
|
||||
$export = '';
|
||||
foreach ($context as $key => $value)
|
||||
{
|
||||
$export.= $key . ': ';
|
||||
$export.= preg_replace(array(
|
||||
'/=>\s+([a-zA-Z])/im',
|
||||
'/array\(\s+\)/im',
|
||||
'/^ |\G /m'
|
||||
),
|
||||
array(
|
||||
'=> $1',
|
||||
'array()',
|
||||
' '
|
||||
),
|
||||
str_replace('array (', 'array(', var_export($value, true))
|
||||
);
|
||||
$export.= PHP_EOL;
|
||||
}
|
||||
return str_replace(array('\\\\', '\\\''), array('\\', '\''), rtrim($export));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indents the given string with the given indent.
|
||||
*
|
||||
* @param string $string The string to indent
|
||||
* @param string $indent What to use as the indent.
|
||||
* @return string
|
||||
*/
|
||||
private function indent($string, $indent = ' ')
|
||||
{
|
||||
return $indent . str_replace("\n", "\n" . $indent, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts level constants to string name.
|
||||
*
|
||||
* @param int $level
|
||||
* @return string
|
||||
*/
|
||||
static function levelToCode($level)
|
||||
{
|
||||
switch ($level)
|
||||
{
|
||||
case self::EMERGENCY:
|
||||
return 'EMERGENCY';
|
||||
case self::ALERT:
|
||||
return 'ALERT';
|
||||
case self::CRITICAL:
|
||||
return 'CRITICAL';
|
||||
case self::NOTICE:
|
||||
return 'NOTICE';
|
||||
case self::INFO:
|
||||
return 'INFO';
|
||||
case self::WARNING:
|
||||
return 'WARNING';
|
||||
case self::DEBUG:
|
||||
return 'DEBUG';
|
||||
case self::ERROR:
|
||||
return 'ERROR';
|
||||
default:
|
||||
throw new RuntimeException('Unknown severity level ' . $level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts level names to constant.
|
||||
*
|
||||
* @param string $code
|
||||
* @return int
|
||||
*/
|
||||
static function codeToLevel($code)
|
||||
{
|
||||
switch (strtoupper($code))
|
||||
{
|
||||
case 'EMERGENCY':
|
||||
return self::EMERGENCY;
|
||||
case 'ALERT':
|
||||
return self::ALERT;
|
||||
case 'CRITICAL':
|
||||
return self::CRITICAL;
|
||||
case 'NOTICE':
|
||||
return self::NOTICE;
|
||||
case 'INFO':
|
||||
return self::INFO;
|
||||
case 'WARNING':
|
||||
return self::WARNING;
|
||||
case 'DEBUG':
|
||||
return self::DEBUG;
|
||||
case 'ERROR':
|
||||
return self::ERROR;
|
||||
default:
|
||||
throw new RuntimeException('Unknown severity code ' . $code);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
|
||||
|
||||
/**
|
||||
* Encode in Base32 based on RFC 4648.
|
||||
* Requires 20% more space than base64
|
||||
* Great for case-insensitive filesystems like Windows and URL's (except for = char which can be excluded using the pad option for urls)
|
||||
*
|
||||
* @author Bryan Ruiz
|
||||
* @url https://www.php.net/manual/en/function.base-convert.php#102232
|
||||
**/
|
||||
class PwgBase32 {
|
||||
private static $map = array(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
|
||||
'=' // padding char
|
||||
);
|
||||
|
||||
private static $flippedMap = array(
|
||||
'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
|
||||
'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
|
||||
'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
|
||||
'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
|
||||
);
|
||||
|
||||
/**
|
||||
* Use padding false when encoding for urls
|
||||
*
|
||||
* @return base32 encoded string
|
||||
**/
|
||||
public static function encode($input, $padding = true)
|
||||
{
|
||||
if (empty($input)) return "";
|
||||
$input = str_split($input);
|
||||
$binaryString = "";
|
||||
for ($i = 0; $i < count($input); $i++) {
|
||||
$binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$fiveBitBinaryArray = str_split($binaryString, 5);
|
||||
$base32 = "";
|
||||
$i = 0;
|
||||
while ($i < count($fiveBitBinaryArray)) {
|
||||
$base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
|
||||
$i++;
|
||||
}
|
||||
if ($padding && ($x = strlen($binaryString) % 40) != 0) {
|
||||
if ($x == 8) $base32 .= str_repeat(self::$map[32], 6);
|
||||
else if ($x == 16) $base32 .= str_repeat(self::$map[32], 4);
|
||||
else if ($x == 24) $base32 .= str_repeat(self::$map[32], 3);
|
||||
else if ($x == 32) $base32 .= self::$map[32];
|
||||
}
|
||||
return $base32;
|
||||
}
|
||||
|
||||
public static function decode($input)
|
||||
{
|
||||
if (empty($input)) return;
|
||||
$paddingCharCount = substr_count($input, self::$map[32]);
|
||||
$allowedValues = array(6, 4, 3, 1, 0);
|
||||
if (!in_array($paddingCharCount, $allowedValues)) return false;
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
if (
|
||||
$paddingCharCount == $allowedValues[$i] &&
|
||||
substr($input, - ($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])
|
||||
) return false;
|
||||
}
|
||||
$input = str_replace('=', '', $input);
|
||||
$input = str_split($input);
|
||||
$binaryString = "";
|
||||
for ($i = 0; $i < count($input); $i = $i + 8) {
|
||||
$x = "";
|
||||
if (!in_array($input[$i], self::$map)) return false;
|
||||
for ($j = 0; $j < 8; $j++) {
|
||||
$x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$eightBits = str_split($x, 8);
|
||||
for ($z = 0; $z < count($eightBits); $z++) {
|
||||
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : "";
|
||||
}
|
||||
}
|
||||
return $binaryString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\menubar
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Manages a set of RegisteredBlock and DisplayBlock.
|
||||
*/
|
||||
class BlockManager
|
||||
{
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var RegisteredBlock[] */
|
||||
protected $registered_blocks = array();
|
||||
/** @var DisplayBlock[] */
|
||||
protected $display_blocks = array();
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a notice that allows plugins of menu blocks to register the blocks.
|
||||
*/
|
||||
public function load_registered_blocks()
|
||||
{
|
||||
trigger_notify('blockmanager_register_blocks', array($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_id()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegisteredBlock[]
|
||||
*/
|
||||
public function get_registered_blocks()
|
||||
{
|
||||
return $this->registered_blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a block with the menu. Usually called in 'blockmanager_register_blocks' event.
|
||||
*
|
||||
* @param RegisteredBlock $block
|
||||
*/
|
||||
public function register_block($block)
|
||||
{
|
||||
if (isset($this->registered_blocks[$block->get_id()]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$this->registered_blocks[$block->get_id()] = $block;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs one time preparation of registered blocks for display.
|
||||
* Triggers 'blockmanager_prepare_display' event where plugins can
|
||||
* reposition or hide blocks
|
||||
*/
|
||||
public function prepare_display()
|
||||
{
|
||||
global $conf;
|
||||
$conf_id = 'blk_'.$this->id;
|
||||
$mb_conf = isset($conf[$conf_id]) ? $conf[$conf_id] : array();
|
||||
if (!is_array($mb_conf))
|
||||
{
|
||||
$mb_conf = @unserialize($mb_conf);
|
||||
}
|
||||
|
||||
$idx = 1;
|
||||
foreach ($this->registered_blocks as $id => $block)
|
||||
{
|
||||
$pos = isset($mb_conf[$id]) ? $mb_conf[$id] : $idx*50;
|
||||
if ($pos>0)
|
||||
{
|
||||
$this->display_blocks[$id] = new DisplayBlock($block);
|
||||
$this->display_blocks[$id]->set_position($pos);
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
$this->sort_blocks();
|
||||
trigger_notify('blockmanager_prepare_display', array($this));
|
||||
$this->sort_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the block is hidden.
|
||||
*
|
||||
* @param string $block_id
|
||||
* @return bool
|
||||
*/
|
||||
public function is_hidden($block_id)
|
||||
{
|
||||
return !isset($this->display_blocks[$block_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a block from the displayed blocks.
|
||||
*
|
||||
* @param string $block_id
|
||||
*/
|
||||
public function hide_block($block_id)
|
||||
{
|
||||
unset($this->display_blocks[$block_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a visible block.
|
||||
*
|
||||
* @param string $block_id
|
||||
* @return DisplayBlock|null
|
||||
*/
|
||||
public function get_block($block_id)
|
||||
{
|
||||
if (isset($this->display_blocks[$block_id]))
|
||||
{
|
||||
return $this->display_blocks[$block_id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the position of a block.
|
||||
*
|
||||
* @param string $block_id
|
||||
* @param int $position
|
||||
*/
|
||||
public function set_block_position($block_id, $position)
|
||||
{
|
||||
if (isset($this->display_blocks[$block_id]))
|
||||
{
|
||||
$this->display_blocks[$block_id]->set_position($position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the blocks.
|
||||
*/
|
||||
protected function sort_blocks()
|
||||
{
|
||||
uasort($this->display_blocks, array('BlockManager', 'cmp_by_position'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for blocks sorting.
|
||||
*/
|
||||
static protected function cmp_by_position($a, $b)
|
||||
{
|
||||
return $a->get_position() - $b->get_position();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the menu and assign the result in a template variable.
|
||||
*
|
||||
* @param string $var
|
||||
* @param string $file
|
||||
*/
|
||||
public function apply($var, $file)
|
||||
{
|
||||
global $template;
|
||||
|
||||
$template->set_filename('menubar', $file);
|
||||
trigger_notify('blockmanager_apply', array($this) );
|
||||
|
||||
foreach ($this->display_blocks as $id=>$block)
|
||||
{
|
||||
if (empty($block->raw_content) and empty($block->template))
|
||||
{
|
||||
$this->hide_block($id);
|
||||
}
|
||||
}
|
||||
$this->sort_blocks();
|
||||
$template->assign('blocks', $this->display_blocks);
|
||||
$template->assign_var_from_handle($var, 'menubar');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a menu block registered in a BlockManager object.
|
||||
*/
|
||||
class RegisteredBlock
|
||||
{
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var string */
|
||||
protected $owner;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $name
|
||||
* @param string $owner
|
||||
*/
|
||||
public function __construct($id, $name, $owner)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->owner = $owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_id()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_owner()
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a menu block ready for display in the BlockManager object.
|
||||
*/
|
||||
class DisplayBlock
|
||||
{
|
||||
/** @var RegisteredBlock */
|
||||
protected $_registeredBlock;
|
||||
/** @var int */
|
||||
protected $_position;
|
||||
/** @var string */
|
||||
protected $_title;
|
||||
|
||||
/** @var mixed */
|
||||
public $data;
|
||||
/** @var string */
|
||||
public $template;
|
||||
/** @var string */
|
||||
public $raw_content;
|
||||
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @param RegisteredBlock $block
|
||||
*/
|
||||
public function __construct($block)
|
||||
{
|
||||
$this->_registeredBlock = $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegisteredBlock
|
||||
*/
|
||||
public function get_block()
|
||||
{
|
||||
return $this->_registeredBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_position()
|
||||
{
|
||||
return $this->_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
*/
|
||||
public function set_position($position)
|
||||
{
|
||||
$this->_position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_title()
|
||||
{
|
||||
if (isset($this->_title))
|
||||
{
|
||||
return $this->_title;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->_registeredBlock->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public function set_title($title)
|
||||
{
|
||||
$this->_title = $title;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
Provides a persistent cache mechanism across multiple page loads/sessions etc...
|
||||
*/
|
||||
abstract class PersistentCache
|
||||
{
|
||||
var $default_lifetime = 86400;
|
||||
protected $instance_key = PHPWG_VERSION;
|
||||
|
||||
/**
|
||||
@return a key that can be safely be used with get/set methods
|
||||
*/
|
||||
function make_key($key)
|
||||
{
|
||||
if ( is_array($key) )
|
||||
{
|
||||
$key = implode('&', $key);
|
||||
}
|
||||
$key .= $this->instance_key;
|
||||
return md5($key);
|
||||
}
|
||||
|
||||
/**
|
||||
Searches for a key in the persistent cache and fills corresponding value.
|
||||
@param string $key
|
||||
@param out mixed $value
|
||||
@return false if the $key is not found in cache ($value is not modified in this case)
|
||||
*/
|
||||
abstract function get($key, &$value);
|
||||
|
||||
/**
|
||||
Sets a key/value pair in the persistent cache.
|
||||
@param string $key - it should be the return value of make_key function
|
||||
@param mixed $value
|
||||
@param int $lifetime
|
||||
@return false on error
|
||||
*/
|
||||
abstract function set($key, $value, $lifetime=null);
|
||||
|
||||
/**
|
||||
Purge the persistent cache.
|
||||
@param boolean $all - if false only expired items will be purged
|
||||
*/
|
||||
abstract function purge($all);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Implementation of a persistent cache using files.
|
||||
*/
|
||||
class PersistentFileCache extends PersistentCache
|
||||
{
|
||||
private $dir;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
global $conf;
|
||||
$this->dir = PHPWG_ROOT_PATH.$conf['data_location'].'cache/';
|
||||
}
|
||||
|
||||
function get($key, &$value)
|
||||
{
|
||||
$loaded = @file_get_contents($this->dir.$key.'.cache');
|
||||
if ($loaded !== false && ($loaded=unserialize($loaded)) !== false)
|
||||
{
|
||||
if ($loaded['expire'] > time())
|
||||
{
|
||||
$value = $loaded['data'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function set($key, $value, $lifetime=null)
|
||||
{
|
||||
if ($lifetime === null)
|
||||
{
|
||||
$lifetime = $this->default_lifetime;
|
||||
}
|
||||
|
||||
if (rand() % 97 == 0)
|
||||
{
|
||||
$this->purge(false);
|
||||
}
|
||||
|
||||
$serialized = serialize( array(
|
||||
'expire' => time() + $lifetime,
|
||||
'data' => $value
|
||||
));
|
||||
|
||||
if (false === @file_put_contents($this->dir.$key.'.cache', $serialized))
|
||||
{
|
||||
mkgetdir($this->dir, MKGETDIR_DEFAULT&~MKGETDIR_DIE_ON_ERROR);
|
||||
if (false === @file_put_contents($this->dir.$key.'.cache', $serialized))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function purge($all)
|
||||
{
|
||||
$files = glob($this->dir.'*.cache');
|
||||
if (empty($files))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = time() - $this->default_lifetime;
|
||||
foreach ($files as $file)
|
||||
{
|
||||
if ($all || @filemtime($file) < $limit)
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\calendar
|
||||
*/
|
||||
|
||||
/** level of year view */
|
||||
define('CYEAR', 0);
|
||||
/** level of week view in weekly view */
|
||||
define('CWEEK', 1);
|
||||
/** level of month view in monthly view */
|
||||
define('CMONTH', 1);
|
||||
/** level of day view */
|
||||
define('CDAY', 2);
|
||||
|
||||
/**
|
||||
* Base class for monthly and weekly calendar styles
|
||||
*/
|
||||
abstract class CalendarBase
|
||||
{
|
||||
/** db column on which this calendar works */
|
||||
var $date_field;
|
||||
/** used for queries (INNER JOIN or normal) */
|
||||
var $inner_sql;
|
||||
/** used to store db fields */
|
||||
var $calendar_levels;
|
||||
|
||||
/**
|
||||
* Generate navigation bars for category page.
|
||||
*
|
||||
* @return boolean false indicates that thumbnails where not included
|
||||
*/
|
||||
abstract function generate_category_content();
|
||||
|
||||
/**
|
||||
* Returns a sql WHERE subquery for the date field.
|
||||
*
|
||||
* @param int $max_levels (e.g. 2=only year and month)
|
||||
* @return string
|
||||
*/
|
||||
abstract function get_date_where($max_levels=3);
|
||||
|
||||
/**
|
||||
* Initialize the calendar.
|
||||
*
|
||||
* @param string $inner_sql
|
||||
*/
|
||||
function initialize($inner_sql)
|
||||
{
|
||||
global $page;
|
||||
if ($page['chronology_field']=='posted')
|
||||
{
|
||||
$this->date_field = 'date_available';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->date_field = 'date_creation';
|
||||
}
|
||||
$this->inner_sql = $inner_sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the calendar title (with HTML).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_display_name()
|
||||
{
|
||||
global $conf, $page;
|
||||
$res = '';
|
||||
|
||||
for ($i=0; $i<count($page['chronology_date']); $i++)
|
||||
{
|
||||
$res .= $conf['level_separator'];
|
||||
if ( isset($page['chronology_date'][$i+1]) )
|
||||
{
|
||||
$chronology_date = array_slice($page['chronology_date'],0, $i+1);
|
||||
$url = duplicate_index_url(
|
||||
array( 'chronology_date'=>$chronology_date ),
|
||||
array( 'start' )
|
||||
);
|
||||
$res .=
|
||||
'<a href="'.$url.'">'
|
||||
.$this->get_date_component_label($i, $page['chronology_date'][$i])
|
||||
.'</a>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$res .=
|
||||
'<span class="calInHere">'
|
||||
.$this->get_date_component_label($i, $page['chronology_date'][$i])
|
||||
.'</span>';
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a display name for a date component optionally using labels.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_date_component_label($level, $date_component)
|
||||
{
|
||||
$label = $date_component;
|
||||
if (isset($this->calendar_levels[$level]['labels'][$date_component]))
|
||||
{
|
||||
$label = $this->calendar_levels[$level]['labels'][$date_component];
|
||||
}
|
||||
elseif ('any' === $date_component )
|
||||
{
|
||||
$label = l10n('All');
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a nice display name for a date to be shown in previous/next links
|
||||
*
|
||||
* @param string $date
|
||||
* @return string
|
||||
*/
|
||||
protected function get_date_nice_name($date)
|
||||
{
|
||||
$date_components = explode('-', $date);
|
||||
$res = '';
|
||||
for ($i=count($date_components)-1; $i>=0; $i--)
|
||||
{
|
||||
if ('any' !== $date_components[$i])
|
||||
{
|
||||
$label = $this->get_date_component_label($i, $date_components[$i] );
|
||||
if ( $res!='' )
|
||||
{
|
||||
$res .= ' ';
|
||||
}
|
||||
$res .= $label;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a calendar navigation bar.
|
||||
*
|
||||
* @param array $date_components
|
||||
* @param array $items - hash of items to put in the bar (e.g. 2005,2006)
|
||||
* @param bool $show_any - adds any link to the end of the bar
|
||||
* @param bool $show_empty - shows all labels even those without items
|
||||
* @param array $labels - optional labels for items (e.g. Jan,Feb,...)
|
||||
* @return string
|
||||
*/
|
||||
protected function get_nav_bar_from_items($date_components, $items,
|
||||
$show_any,
|
||||
$show_empty=false, $labels=null)
|
||||
{
|
||||
global $conf, $page, $template;
|
||||
|
||||
$nav_bar_datas=array();
|
||||
|
||||
if ($conf['calendar_show_empty'] and $show_empty and !empty($labels) )
|
||||
{
|
||||
foreach ($labels as $item => $label)
|
||||
{
|
||||
if ( ! isset($items[$item]) )
|
||||
{
|
||||
$items[$item] = -1;
|
||||
}
|
||||
}
|
||||
ksort($items);
|
||||
}
|
||||
|
||||
foreach ($items as $item => $nb_images)
|
||||
{
|
||||
$label = $item;
|
||||
if (isset($labels[$item]))
|
||||
{
|
||||
$label = $labels[$item];
|
||||
}
|
||||
if ($nb_images==-1)
|
||||
{
|
||||
$tmp_datas=array(
|
||||
'LABEL'=> $label
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = duplicate_index_url(
|
||||
array('chronology_date'=>array_merge($date_components,array($item))),
|
||||
array( 'start' )
|
||||
);
|
||||
$tmp_datas=array(
|
||||
'LABEL'=> $label,
|
||||
'URL' => $url
|
||||
);
|
||||
}
|
||||
if ($nb_images > 0)
|
||||
{
|
||||
$tmp_datas['NB_IMAGES']=$nb_images;
|
||||
}
|
||||
$nav_bar_datas[]=$tmp_datas;
|
||||
|
||||
}
|
||||
|
||||
if ($conf['calendar_show_any'] and $show_any and count($items)>1 and
|
||||
count($date_components)<count($this->calendar_levels)-1 )
|
||||
{
|
||||
$url = duplicate_index_url(
|
||||
array('chronology_date'=>array_merge($date_components,array('any'))),
|
||||
array( 'start' )
|
||||
);
|
||||
$nav_bar_datas[]=array(
|
||||
'LABEL' => l10n('All'),
|
||||
'URL' => $url
|
||||
);
|
||||
}
|
||||
|
||||
return $nav_bar_datas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a calendar navigation bar for a given level.
|
||||
*
|
||||
* @param int $level - 0-year, 1-month/week, 2-day
|
||||
*/
|
||||
protected function build_nav_bar($level, $labels=null)
|
||||
{
|
||||
global $template, $conf, $page;
|
||||
|
||||
$query = '
|
||||
SELECT DISTINCT('.$this->calendar_levels[$level]['sql'].') as period,
|
||||
COUNT(DISTINCT id) as nb_images'.
|
||||
$this->inner_sql.
|
||||
$this->get_date_where($level).'
|
||||
GROUP BY period;';
|
||||
|
||||
$level_items = query2array($query, 'period', 'nb_images');
|
||||
|
||||
if ( count($level_items)==1 and
|
||||
count($page['chronology_date'])<count($this->calendar_levels)-1)
|
||||
{
|
||||
if ( ! isset($page['chronology_date'][$level]) )
|
||||
{
|
||||
list($key) = array_keys($level_items);
|
||||
$page['chronology_date'][$level] = (int)$key;
|
||||
|
||||
if ( $level<count($page['chronology_date']) and
|
||||
$level!=count($this->calendar_levels)-1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dates = $page['chronology_date'];
|
||||
while ($level<count($dates))
|
||||
{
|
||||
array_pop($dates);
|
||||
}
|
||||
|
||||
$nav_bar = $this->get_nav_bar_from_items(
|
||||
$dates,
|
||||
$level_items,
|
||||
true,
|
||||
true,
|
||||
isset($labels) ? $labels : $this->calendar_levels[$level]['labels']
|
||||
);
|
||||
|
||||
$template->append(
|
||||
'chronology_navigation_bars',
|
||||
array(
|
||||
'items' => $nav_bar,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the next/previous link to the template with regards to
|
||||
* the currently choosen date.
|
||||
*/
|
||||
protected function build_next_prev()
|
||||
{
|
||||
global $template, $page;
|
||||
|
||||
$prev = $next =null;
|
||||
if ( empty($page['chronology_date']) )
|
||||
return;
|
||||
|
||||
$sub_queries = array();
|
||||
$nb_elements = count($page['chronology_date']);
|
||||
for ($i=0; $i<$nb_elements; $i++)
|
||||
{
|
||||
if ( 'any' === $page['chronology_date'][$i] )
|
||||
{
|
||||
$sub_queries[] = '\'any\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sub_queries[] = pwg_db_cast_to_text($this->calendar_levels[$i]['sql']);
|
||||
}
|
||||
}
|
||||
$query = 'SELECT '.pwg_db_concat_ws($sub_queries, '-').' AS period';
|
||||
$query .= $this->inner_sql .'
|
||||
AND ' . $this->date_field . ' IS NOT NULL
|
||||
GROUP BY period';
|
||||
|
||||
$current = implode('-', $page['chronology_date'] );
|
||||
$upper_items = query2array($query,null, 'period');
|
||||
|
||||
usort($upper_items, 'version_compare');
|
||||
$upper_items_rank = array_flip($upper_items);
|
||||
if ( !isset($upper_items_rank[$current]) )
|
||||
{
|
||||
$upper_items[] = $current;// just in case (external link)
|
||||
usort($upper_items, 'version_compare');
|
||||
$upper_items_rank = array_flip($upper_items);
|
||||
}
|
||||
$current_rank = $upper_items_rank[$current];
|
||||
|
||||
$tpl_var = array();
|
||||
|
||||
if ( $current_rank>0 )
|
||||
{ // has previous
|
||||
$prev = $upper_items[$current_rank-1];
|
||||
$chronology_date = explode('-', $prev);
|
||||
$tpl_var['previous'] =
|
||||
array(
|
||||
'LABEL' => $this->get_date_nice_name($prev),
|
||||
'URL' => duplicate_index_url(
|
||||
array('chronology_date'=>$chronology_date), array('start')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $current_rank < count($upper_items)-1 )
|
||||
{ // has next
|
||||
$next = $upper_items[$current_rank+1];
|
||||
$chronology_date = explode('-', $next);
|
||||
$tpl_var['next'] =
|
||||
array(
|
||||
'LABEL' => $this->get_date_nice_name($next),
|
||||
'URL' => duplicate_index_url(
|
||||
array('chronology_date'=>$chronology_date), array('start')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( !empty($tpl_var) )
|
||||
{
|
||||
$existing = $template->smarty->getTemplateVars('chronology_navigation_bars');
|
||||
if (!empty($existing))
|
||||
{
|
||||
$existing[ sizeof($existing)-1 ] = array_merge( $existing[ sizeof($existing)-1 ], $tpl_var);
|
||||
$template->assign('chronology_navigation_bars', $existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->append( 'chronology_navigation_bars', $tpl_var );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,474 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\calendar
|
||||
*/
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/calendar_base.class.php');
|
||||
|
||||
/**
|
||||
* Monthly calendar style (composed of years/months and days)
|
||||
*/
|
||||
class CalendarMonthly extends CalendarBase
|
||||
{
|
||||
/**
|
||||
* Initialize the calendar.
|
||||
* @param string $inner_sql
|
||||
*/
|
||||
function initialize($inner_sql)
|
||||
{
|
||||
parent::initialize($inner_sql);
|
||||
global $lang;
|
||||
$this->calendar_levels = array(
|
||||
array(
|
||||
'sql'=> pwg_db_get_year($this->date_field),
|
||||
'labels' => null
|
||||
),
|
||||
array(
|
||||
'sql'=> pwg_db_get_month($this->date_field),
|
||||
'labels' => $lang['month']
|
||||
),
|
||||
array(
|
||||
'sql'=> pwg_db_get_dayofmonth($this->date_field),
|
||||
'labels' => null
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate navigation bars for category page.
|
||||
*
|
||||
* @return boolean false indicates that thumbnails where not included
|
||||
*/
|
||||
function generate_category_content()
|
||||
{
|
||||
global $conf, $page;
|
||||
|
||||
$view_type = $page['chronology_view'];
|
||||
if ($view_type==CAL_VIEW_CALENDAR)
|
||||
{
|
||||
global $template;
|
||||
$tpl_var = array();
|
||||
if ( count($page['chronology_date'])==0 )
|
||||
{//case A: no year given - display all years+months
|
||||
if ($this->build_global_calendar($tpl_var))
|
||||
{
|
||||
$template->assign('chronology_calendar', $tpl_var);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count($page['chronology_date'])==1 )
|
||||
{//case B: year given - display all days in given year
|
||||
if ($this->build_year_calendar($tpl_var))
|
||||
{
|
||||
$template->assign('chronology_calendar', $tpl_var);
|
||||
$this->build_nav_bar(CYEAR); // years
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count($page['chronology_date'])==2 )
|
||||
{//case C: year+month given - display a nice month calendar
|
||||
if ( $this->build_month_calendar($tpl_var) )
|
||||
{
|
||||
$template->assign('chronology_calendar', $tpl_var);
|
||||
}
|
||||
$this->build_next_prev();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($view_type==CAL_VIEW_LIST or count($page['chronology_date'])==3)
|
||||
{
|
||||
if ( count($page['chronology_date'])==0 )
|
||||
{
|
||||
$this->build_nav_bar(CYEAR); // years
|
||||
}
|
||||
if ( count($page['chronology_date'])==1)
|
||||
{
|
||||
$this->build_nav_bar(CMONTH); // month
|
||||
}
|
||||
if ( count($page['chronology_date'])==2 )
|
||||
{
|
||||
$day_labels = range( 1, $this->get_all_days_in_month(
|
||||
$page['chronology_date'][CYEAR] ,$page['chronology_date'][CMONTH] ) );
|
||||
array_unshift($day_labels, 0);
|
||||
unset( $day_labels[0] );
|
||||
$this->build_nav_bar( CDAY, $day_labels ); // days
|
||||
}
|
||||
$this->build_next_prev();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sql WHERE subquery for the date field.
|
||||
*
|
||||
* @param int $max_levels (e.g. 2=only year and month)
|
||||
* @return string
|
||||
*/
|
||||
function get_date_where($max_levels=3)
|
||||
{
|
||||
global $page;
|
||||
|
||||
$date = $page['chronology_date'];
|
||||
while (count($date)>$max_levels)
|
||||
{
|
||||
array_pop($date);
|
||||
}
|
||||
$res = '';
|
||||
if (isset($date[CYEAR]) and $date[CYEAR]!=='any')
|
||||
{
|
||||
$b = $date[CYEAR] . '-';
|
||||
$e = $date[CYEAR] . '-';
|
||||
if (isset($date[CMONTH]) and $date[CMONTH]!=='any')
|
||||
{
|
||||
$b .= sprintf('%02d-', $date[CMONTH]);
|
||||
$e .= sprintf('%02d-', $date[CMONTH]);
|
||||
if (isset($date[CDAY]) and $date[CDAY]!=='any')
|
||||
{
|
||||
$b .= sprintf('%02d', $date[CDAY]);
|
||||
$e .= sprintf('%02d', $date[CDAY]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$b .= '01';
|
||||
$e .= $this->get_all_days_in_month($date[CYEAR], $date[CMONTH]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$b .= '01-01';
|
||||
$e .= '12-31';
|
||||
if (isset($date[CMONTH]) and $date[CMONTH]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CMONTH]['sql'].'='.$date[CMONTH];
|
||||
}
|
||||
if (isset($date[CDAY]) and $date[CDAY]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CDAY]['sql'].'='.$date[CDAY];
|
||||
}
|
||||
}
|
||||
$res = " AND $this->date_field BETWEEN '$b' AND '$e 23:59:59'" . $res;
|
||||
}
|
||||
else
|
||||
{
|
||||
$res = ' AND '.$this->date_field.' IS NOT NULL';
|
||||
if (isset($date[CMONTH]) and $date[CMONTH]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CMONTH]['sql'].'='.$date[CMONTH];
|
||||
}
|
||||
if (isset($date[CDAY]) and $date[CDAY]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CDAY]['sql'].'='.$date[CDAY];
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the days in a given month.
|
||||
*
|
||||
* @param int $year
|
||||
* @param int $month
|
||||
* @return int[]
|
||||
*/
|
||||
protected function get_all_days_in_month($year, $month)
|
||||
{
|
||||
$md= array(1=>31,28,31,30,31,30,31,31,30,31,30,31);
|
||||
|
||||
if ( is_numeric($year) and $month==2)
|
||||
{
|
||||
$nb_days = $md[2];
|
||||
if ( ($year%4==0) and ( ($year%100!=0) or ($year%400!=0) ) )
|
||||
{
|
||||
$nb_days++;
|
||||
}
|
||||
}
|
||||
elseif ( is_numeric($month) )
|
||||
{
|
||||
$nb_days = $md[ $month ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$nb_days = 31;
|
||||
}
|
||||
return $nb_days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build global calendar and assign the result in _$tpl_var_
|
||||
*
|
||||
* @param array $tpl_var
|
||||
* @return bool
|
||||
*/
|
||||
protected function build_global_calendar(&$tpl_var)
|
||||
{
|
||||
global $page;
|
||||
|
||||
assert( count($page['chronology_date']) == 0 );
|
||||
$query='
|
||||
SELECT '.pwg_db_get_date_YYYYMM($this->date_field).' as period,
|
||||
COUNT(distinct id) as count';
|
||||
$query.= $this->inner_sql;
|
||||
$query.= $this->get_date_where();
|
||||
$query.= '
|
||||
GROUP BY period
|
||||
ORDER BY '.pwg_db_get_year($this->date_field).' DESC, '.pwg_db_get_month($this->date_field).' ASC';
|
||||
|
||||
$result = pwg_query($query);
|
||||
$items=array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$y = substr($row['period'], 0, 4);
|
||||
$m = (int)substr($row['period'], 4, 2);
|
||||
if ( ! isset($items[$y]) )
|
||||
{
|
||||
$items[$y] = array('nb_images'=>0, 'children'=>array() );
|
||||
}
|
||||
$items[$y]['children'][$m] = $row['count'];
|
||||
$items[$y]['nb_images'] += $row['count'];
|
||||
}
|
||||
//echo ('<pre>'. var_export($items, true) . '</pre>');
|
||||
if (count($items)==1)
|
||||
{// only one year exists so bail out to year view
|
||||
list($y) = array_keys($items);
|
||||
$page['chronology_date'][CYEAR] = $y;
|
||||
return false;
|
||||
}
|
||||
|
||||
global $lang;
|
||||
foreach ( $items as $year=>$year_data)
|
||||
{
|
||||
$chronology_date = array( $year );
|
||||
$url = duplicate_index_url( array('chronology_date'=>$chronology_date) );
|
||||
|
||||
$nav_bar = $this->get_nav_bar_from_items( $chronology_date,
|
||||
$year_data['children'], false, false, $lang['month'] );
|
||||
|
||||
$tpl_var['calendar_bars'][] =
|
||||
array(
|
||||
'U_HEAD' => $url,
|
||||
'NB_IMAGES' => $year_data['nb_images'],
|
||||
'HEAD_LABEL' => $year,
|
||||
'items' => $nav_bar,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build year calendar and assign the result in _$tpl_var_
|
||||
*
|
||||
* @param array $tpl_var
|
||||
* @return bool
|
||||
*/
|
||||
protected function build_year_calendar(&$tpl_var)
|
||||
{
|
||||
global $page;
|
||||
|
||||
assert( count($page['chronology_date']) == 1 );
|
||||
$query='SELECT '.pwg_db_get_date_MMDD($this->date_field).' as period,
|
||||
COUNT(DISTINCT id) as count';
|
||||
$query.= $this->inner_sql;
|
||||
$query.= $this->get_date_where();
|
||||
$query.= '
|
||||
GROUP BY period
|
||||
ORDER BY period ASC';
|
||||
|
||||
$result = pwg_query($query);
|
||||
$items=array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$m = (int)substr($row['period'], 0, 2);
|
||||
$d = substr($row['period'], 2, 2);
|
||||
if ( ! isset($items[$m]) )
|
||||
{
|
||||
$items[$m] = array('nb_images'=>0, 'children'=>array() );
|
||||
}
|
||||
$items[$m]['children'][$d] = $row['count'];
|
||||
$items[$m]['nb_images'] += $row['count'];
|
||||
}
|
||||
if (count($items)==1)
|
||||
{ // only one month exists so bail out to month view
|
||||
list($m) = array_keys($items);
|
||||
$page['chronology_date'][CMONTH] = $m;
|
||||
return false;
|
||||
}
|
||||
global $lang;
|
||||
foreach ( $items as $month=>$month_data)
|
||||
{
|
||||
$chronology_date = array( $page['chronology_date'][CYEAR], $month );
|
||||
$url = duplicate_index_url( array('chronology_date'=>$chronology_date) );
|
||||
|
||||
$nav_bar = $this->get_nav_bar_from_items( $chronology_date,
|
||||
$month_data['children'], false );
|
||||
|
||||
$tpl_var['calendar_bars'][] =
|
||||
array(
|
||||
'U_HEAD' => $url,
|
||||
'NB_IMAGES' => $month_data['nb_images'],
|
||||
'HEAD_LABEL' => $lang['month'][$month],
|
||||
'items' => $nav_bar,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build month calendar and assign the result in _$tpl_var_
|
||||
*
|
||||
* @param array $tpl_var
|
||||
* @return bool
|
||||
*/
|
||||
protected function build_month_calendar(&$tpl_var)
|
||||
{
|
||||
global $page, $lang, $conf;
|
||||
|
||||
$query='SELECT '.pwg_db_get_dayofmonth($this->date_field).' as period,
|
||||
COUNT(DISTINCT id) as count';
|
||||
$query.= $this->inner_sql;
|
||||
$query.= $this->get_date_where();
|
||||
$query.= '
|
||||
GROUP BY period
|
||||
ORDER BY period ASC';
|
||||
|
||||
$items=array();
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$d = (int)$row['period'];
|
||||
$items[$d] = array('nb_images'=>$row['count']);
|
||||
}
|
||||
|
||||
foreach ( $items as $day=>$data)
|
||||
{
|
||||
$page['chronology_date'][CDAY]=$day;
|
||||
$query = '
|
||||
SELECT id, file,representative_ext,path,width,height,rotation, '.pwg_db_get_dayofweek($this->date_field).'-1 as dow';
|
||||
$query.= $this->inner_sql;
|
||||
$query.= $this->get_date_where();
|
||||
$query.= '
|
||||
ORDER BY '.DB_RANDOM_FUNCTION.'()
|
||||
LIMIT 1';
|
||||
unset ( $page['chronology_date'][CDAY] );
|
||||
|
||||
$row = pwg_db_fetch_assoc(pwg_query($query));
|
||||
$derivative = new DerivativeImage(IMG_SQUARE, new SrcImage($row));
|
||||
$items[$day]['derivative'] = $derivative;
|
||||
$items[$day]['file'] = $row['file'];
|
||||
$items[$day]['dow'] = $row['dow'];
|
||||
}
|
||||
|
||||
if ( !empty($items) )
|
||||
{
|
||||
list($known_day) = array_keys($items);
|
||||
$known_dow = $items[$known_day]['dow'];
|
||||
$first_day_dow = ($known_dow-($known_day-1))%7;
|
||||
if ($first_day_dow<0)
|
||||
{
|
||||
$first_day_dow += 7;
|
||||
}
|
||||
//first_day_dow = week day corresponding to the first day of this month
|
||||
$wday_labels = $lang['day'];
|
||||
|
||||
if ('monday' == $conf['week_starts_on'])
|
||||
{
|
||||
if ($first_day_dow==0)
|
||||
{
|
||||
$first_day_dow = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
$first_day_dow -= 1;
|
||||
}
|
||||
|
||||
$wday_labels[] = array_shift($wday_labels);
|
||||
}
|
||||
|
||||
list($cell_width, $cell_height) = ImageStdParams::get_by_type(IMG_SQUARE)->sizing->ideal_size;
|
||||
|
||||
$tpl_weeks = array();
|
||||
$tpl_crt_week = array();
|
||||
|
||||
//fill the empty days in the week before first day of this month
|
||||
for ($i=0; $i<$first_day_dow; $i++)
|
||||
{
|
||||
$tpl_crt_week[] = array();
|
||||
}
|
||||
|
||||
for ( $day = 1;
|
||||
$day <= $this->get_all_days_in_month(
|
||||
$page['chronology_date'][CYEAR], $page['chronology_date'][CMONTH]
|
||||
);
|
||||
$day++)
|
||||
{
|
||||
$dow = ($first_day_dow + $day-1)%7;
|
||||
if ($dow==0 and $day!=1)
|
||||
{
|
||||
$tpl_weeks[] = $tpl_crt_week; // add finished week to week list
|
||||
$tpl_crt_week = array(); // start new week
|
||||
}
|
||||
|
||||
if ( !isset($items[$day]) )
|
||||
{// empty day
|
||||
$tpl_crt_week[] =
|
||||
array(
|
||||
'DAY' => $day
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = duplicate_index_url(
|
||||
array(
|
||||
'chronology_date' =>
|
||||
array(
|
||||
$page['chronology_date'][CYEAR],
|
||||
$page['chronology_date'][CMONTH],
|
||||
$day
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$tpl_crt_week[] =
|
||||
array(
|
||||
'DAY' => $day,
|
||||
'DOW' => $dow,
|
||||
'NB_ELEMENTS' => $items[$day]['nb_images'],
|
||||
'IMAGE' => $items[$day]['derivative']->get_url(),
|
||||
'U_IMG_LINK' => $url,
|
||||
'IMAGE_ALT' => $items[$day]['file'],
|
||||
);
|
||||
}
|
||||
}
|
||||
//fill the empty days in the week after the last day of this month
|
||||
while ( $dow<6 )
|
||||
{
|
||||
$tpl_crt_week[] = array();
|
||||
$dow++;
|
||||
}
|
||||
$tpl_weeks[] = $tpl_crt_week;
|
||||
|
||||
$tpl_var['month_view'] =
|
||||
array(
|
||||
'CELL_WIDTH' => $cell_width,
|
||||
'CELL_HEIGHT' => $cell_height,
|
||||
'wday_labels' => $wday_labels,
|
||||
'weeks' => $tpl_weeks,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\calendar
|
||||
*/
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/calendar_base.class.php');
|
||||
|
||||
/**
|
||||
* Weekly calendar style (composed of years/week in years and days in week)
|
||||
*/
|
||||
class CalendarWeekly extends CalendarBase
|
||||
{
|
||||
/**
|
||||
* Initialize the calendar
|
||||
* @param string $inner_sql
|
||||
*/
|
||||
function initialize($inner_sql)
|
||||
{
|
||||
parent::initialize($inner_sql);
|
||||
global $lang, $conf;
|
||||
$week_no_labels=array();
|
||||
for ($i=1; $i<=53; $i++)
|
||||
{
|
||||
$week_no_labels[$i] = l10n('Week %d', $i);
|
||||
//$week_no_labels[$i] = $i;
|
||||
}
|
||||
|
||||
$this->calendar_levels = array(
|
||||
array(
|
||||
'sql'=> pwg_db_get_year($this->date_field),
|
||||
'labels' => null
|
||||
),
|
||||
array(
|
||||
'sql'=> pwg_db_get_week($this->date_field).'+1',
|
||||
'labels' => $week_no_labels,
|
||||
),
|
||||
array(
|
||||
'sql'=> pwg_db_get_dayofweek($this->date_field).'-1',
|
||||
'labels' => $lang['day']
|
||||
),
|
||||
);
|
||||
//Comment next lines for week starting on Sunday or if MySQL version<4.0.17
|
||||
//WEEK(date,5) = "0-53 - Week 1=the first week with a Monday in this year"
|
||||
if ('monday' == $conf['week_starts_on'])
|
||||
{
|
||||
$this->calendar_levels[CWEEK]['sql'] = pwg_db_get_week($this->date_field, 5).'+1';
|
||||
$this->calendar_levels[CDAY]['sql'] = pwg_db_get_weekday($this->date_field);
|
||||
$this->calendar_levels[CDAY]['labels'][] = array_shift($this->calendar_levels[CDAY]['labels']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate navigation bars for category page.
|
||||
*
|
||||
* @return boolean false indicates that thumbnails where not included
|
||||
*/
|
||||
function generate_category_content()
|
||||
{
|
||||
global $conf, $page;
|
||||
|
||||
if ( count($page['chronology_date'])==0 )
|
||||
{
|
||||
$this->build_nav_bar(CYEAR); // years
|
||||
}
|
||||
if ( count($page['chronology_date'])==1 )
|
||||
{
|
||||
$this->build_nav_bar(CWEEK, array()); // week nav bar 1-53
|
||||
}
|
||||
if ( count($page['chronology_date'])==2 )
|
||||
{
|
||||
$this->build_nav_bar(CDAY); // days nav bar Mon-Sun
|
||||
}
|
||||
$this->build_next_prev();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sql WHERE subquery for the date field.
|
||||
*
|
||||
* @param int $max_levels (e.g. 2=only year and month)
|
||||
* @return string
|
||||
*/
|
||||
function get_date_where($max_levels=3)
|
||||
{
|
||||
global $page;
|
||||
$date = $page['chronology_date'];
|
||||
while (count($date)>$max_levels)
|
||||
{
|
||||
array_pop($date);
|
||||
}
|
||||
$res = '';
|
||||
if (isset($date[CYEAR]) and $date[CYEAR]!=='any')
|
||||
{
|
||||
$y = $date[CYEAR];
|
||||
$res = " AND $this->date_field BETWEEN '$y-01-01' AND '$y-12-31 23:59:59'";
|
||||
}
|
||||
|
||||
if (isset($date[CWEEK]) and $date[CWEEK]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CWEEK]['sql'].'='.$date[CWEEK];
|
||||
}
|
||||
if (isset($date[CDAY]) and $date[CDAY]!=='any')
|
||||
{
|
||||
$res .= ' AND '.$this->calendar_levels[CDAY]['sql'].'='.$date[CDAY];
|
||||
}
|
||||
if (empty($res))
|
||||
{
|
||||
$res = ' AND '.$this->date_field.' IS NOT NULL';
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This file is included by the main page to show subcategories of a category
|
||||
* or to show recent categories or main page categories list
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// $user['forbidden_categories'] including with USER_CACHE_CATEGORIES_TABLE
|
||||
$query = '
|
||||
SELECT SQL_CALC_FOUND_ROWS
|
||||
c.*,
|
||||
user_representative_picture_id,
|
||||
nb_images,
|
||||
date_last,
|
||||
max_date_last,
|
||||
count_images,
|
||||
nb_categories,
|
||||
count_categories
|
||||
FROM '.CATEGORIES_TABLE.' c
|
||||
INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' ucc
|
||||
ON id = cat_id
|
||||
AND user_id = '.$user['id'].'
|
||||
WHERE count_images > 0
|
||||
';
|
||||
|
||||
if ('recent_cats' == $page['section'])
|
||||
{
|
||||
$query.= '
|
||||
AND '.get_recent_photos_sql('date_last');
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= '
|
||||
AND id_uppercat '.(!isset($page['category']) ? 'is NULL' : '= '.$page['category']['id']);
|
||||
}
|
||||
|
||||
$query.= '
|
||||
'.get_sql_condition_FandF(
|
||||
array('visible_categories' => 'id'),
|
||||
'AND'
|
||||
);
|
||||
|
||||
// special string to let plugins modify this query at this exact position
|
||||
$query.= '
|
||||
-- after conditions
|
||||
';
|
||||
|
||||
if ('recent_cats' != $page['section'])
|
||||
{
|
||||
$query.= '
|
||||
ORDER BY `rank`';
|
||||
}
|
||||
|
||||
$query.= '
|
||||
LIMIT '.$conf['nb_categories_page'].' OFFSET '.($page['startcat'] ?? 0).'
|
||||
;';
|
||||
|
||||
$query = trigger_change('loc_begin_index_category_thumbnails_query', $query);
|
||||
|
||||
$result = pwg_query($query);
|
||||
list($page['total_categories']) = pwg_db_fetch_row(pwg_query('SELECT FOUND_ROWS()'));
|
||||
|
||||
$categories = array();
|
||||
$category_ids = array();
|
||||
$image_ids = array();
|
||||
$user_representative_updates_for = array();
|
||||
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$row['is_child_date_last'] = @$row['max_date_last']>@$row['date_last'];
|
||||
|
||||
if (!empty($row['user_representative_picture_id']))
|
||||
{
|
||||
$image_id = $row['user_representative_picture_id'];
|
||||
}
|
||||
elseif (!empty($row['representative_picture_id']))
|
||||
{ // if a representative picture is set, it has priority
|
||||
$image_id = $row['representative_picture_id'];
|
||||
}
|
||||
elseif ($conf['allow_random_representative'])
|
||||
{ // searching a random representant among elements in sub-categories
|
||||
$image_id = get_random_image_in_category($row);
|
||||
}
|
||||
elseif ($row['count_categories']>0 and $row['count_images']>0) // at this point, $row['count_images'] should always be >0 (used as condition in SQL)
|
||||
{ // searching a random representant among representant of sub-categories
|
||||
$query = '
|
||||
SELECT representative_picture_id
|
||||
FROM '.CATEGORIES_TABLE.' INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.'
|
||||
ON id = cat_id and user_id = '.$user['id'].'
|
||||
WHERE uppercats LIKE \''.$row['uppercats'].',%\'
|
||||
AND representative_picture_id IS NOT NULL'
|
||||
.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'visible_categories' => 'id',
|
||||
),
|
||||
"\n AND"
|
||||
).'
|
||||
ORDER BY '.DB_RANDOM_FUNCTION.'()
|
||||
LIMIT 1
|
||||
;';
|
||||
$subresult = pwg_query($query);
|
||||
if (pwg_db_num_rows($subresult) > 0)
|
||||
{
|
||||
list($image_id) = pwg_db_fetch_row($subresult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isset($image_id))
|
||||
{
|
||||
if ($conf['representative_cache_on_subcats'] and $row['user_representative_picture_id'] != $image_id)
|
||||
{
|
||||
$user_representative_updates_for[ $row['id'] ] = $image_id;
|
||||
}
|
||||
|
||||
$row['representative_picture_id'] = $image_id;
|
||||
$image_ids[] = $image_id;
|
||||
$categories[] = $row;
|
||||
$category_ids[] = $row['id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger->info(
|
||||
sprintf(
|
||||
'[%s] category #%u was listed in SQL but no image_id found, so it was skipped',
|
||||
basename(__FILE__),
|
||||
$row['id']
|
||||
)
|
||||
);
|
||||
}
|
||||
unset($image_id);
|
||||
}
|
||||
|
||||
if ($conf['display_fromto'])
|
||||
{
|
||||
if (count($category_ids) > 0)
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
category_id,
|
||||
MIN(date_creation) AS `from`,
|
||||
MAX(date_creation) AS `to`
|
||||
FROM '.IMAGE_CATEGORY_TABLE.'
|
||||
INNER JOIN '.IMAGES_TABLE.' ON image_id = id
|
||||
WHERE category_id IN ('.implode(',', $category_ids).')
|
||||
'.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
'AND'
|
||||
).'
|
||||
GROUP BY category_id
|
||||
;';
|
||||
$dates_of_category = query2array($query, 'category_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($page['section']=='recent_cats')
|
||||
{
|
||||
usort($categories, 'global_rank_compare');
|
||||
}
|
||||
|
||||
if (count($categories) > 0)
|
||||
{
|
||||
$infos_of_image = array();
|
||||
$new_image_ids = array();
|
||||
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.IMAGES_TABLE.'
|
||||
WHERE id IN ('.implode(',', $image_ids).')
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if ($row['level'] <= $user['level'])
|
||||
{
|
||||
$infos_of_image[$row['id']] = $row;
|
||||
}
|
||||
else
|
||||
{
|
||||
// problem: we must not display the thumbnail of a photo which has a
|
||||
// higher privacy level than user privacy level
|
||||
//
|
||||
// * what is the represented category?
|
||||
// * find a random photo matching user permissions
|
||||
// * register it at user_representative_picture_id
|
||||
// * set it as the representative_picture_id for the category
|
||||
|
||||
foreach ($categories as &$category)
|
||||
{
|
||||
if ($row['id'] == $category['representative_picture_id'])
|
||||
{
|
||||
// searching a random representant among elements in sub-categories
|
||||
$image_id = get_random_image_in_category($category);
|
||||
|
||||
if (isset($image_id) and !in_array($image_id, $image_ids))
|
||||
{
|
||||
$new_image_ids[] = $image_id;
|
||||
}
|
||||
|
||||
if ($conf['representative_cache_on_level'])
|
||||
{
|
||||
$user_representative_updates_for[ $category['id'] ] = $image_id;
|
||||
}
|
||||
|
||||
$category['representative_picture_id'] = $image_id;
|
||||
}
|
||||
}
|
||||
unset($category);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($new_image_ids) > 0)
|
||||
{
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.IMAGES_TABLE.'
|
||||
WHERE id IN ('.implode(',', $new_image_ids).')
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$infos_of_image[$row['id']] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($infos_of_image as &$info)
|
||||
{
|
||||
$info['src_image'] = new SrcImage($info);
|
||||
}
|
||||
unset($info);
|
||||
}
|
||||
|
||||
if (count($user_representative_updates_for))
|
||||
{
|
||||
$updates = array();
|
||||
|
||||
foreach ($user_representative_updates_for as $cat_id => $image_id)
|
||||
{
|
||||
$updates[] =
|
||||
array(
|
||||
'user_id' => $user['id'],
|
||||
'cat_id' => $cat_id,
|
||||
'user_representative_picture_id' => $image_id,
|
||||
);
|
||||
}
|
||||
|
||||
mass_updates(
|
||||
USER_CACHE_CATEGORIES_TABLE,
|
||||
array(
|
||||
'primary' => array('user_id', 'cat_id'),
|
||||
'update' => array('user_representative_picture_id')
|
||||
),
|
||||
$updates
|
||||
);
|
||||
}
|
||||
|
||||
if (count($categories) > 0)
|
||||
{
|
||||
// Update filtered data
|
||||
if (function_exists('update_cats_with_filtered_data'))
|
||||
{
|
||||
update_cats_with_filtered_data($categories);
|
||||
}
|
||||
|
||||
$template->set_filename('index_category_thumbnails', 'mainpage_categories.tpl');
|
||||
|
||||
trigger_notify('loc_begin_index_category_thumbnails', $categories);
|
||||
|
||||
$tpl_thumbnails_var = array();
|
||||
|
||||
foreach ($categories as $category)
|
||||
{
|
||||
if (0 == $category['count_images'])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$category['name'] = trigger_change(
|
||||
'render_category_name',
|
||||
$category['name'],
|
||||
'subcatify_category_name'
|
||||
);
|
||||
|
||||
if ($page['section']=='recent_cats')
|
||||
{
|
||||
$name = get_cat_display_name_cache($category['uppercats'], null);
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = $category['name'];
|
||||
}
|
||||
|
||||
$representative_infos = $infos_of_image[ $category['representative_picture_id'] ];
|
||||
|
||||
$tpl_var = array_merge( $category, array(
|
||||
'ID' => $category['id'] /*obsolete*/,
|
||||
'representative' => $representative_infos,
|
||||
'TN_ALT' => strip_tags($category['name']),
|
||||
|
||||
'URL' => make_index_url(
|
||||
array(
|
||||
'category' => $category
|
||||
)
|
||||
),
|
||||
'CAPTION_NB_IMAGES' => get_display_images_count
|
||||
(
|
||||
$category['nb_images'],
|
||||
$category['count_images'],
|
||||
$category['count_categories'],
|
||||
true,
|
||||
'<br>'
|
||||
),
|
||||
'DESCRIPTION' =>
|
||||
trigger_change('render_category_literal_description',
|
||||
trigger_change('render_category_description',
|
||||
@$category['comment'],
|
||||
'subcatify_category_description')),
|
||||
'NAME' => $name,
|
||||
) );
|
||||
if ($conf['index_new_icon'])
|
||||
{
|
||||
$tpl_var['icon_ts'] = get_icon($category['max_date_last'], $category['is_child_date_last']);
|
||||
}
|
||||
|
||||
if ($conf['display_fromto'])
|
||||
{
|
||||
if (isset($dates_of_category[ $category['id'] ]))
|
||||
{
|
||||
$from = $dates_of_category[ $category['id'] ]['from'];
|
||||
$to = $dates_of_category[ $category['id'] ]['to'];
|
||||
|
||||
if (!empty($from))
|
||||
{
|
||||
$tpl_var['INFO_DATES'] = format_fromto($from, $to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tpl_thumbnails_var[] = $tpl_var;
|
||||
}
|
||||
|
||||
// pagination
|
||||
$tpl_thumbnails_var_selection = $tpl_thumbnails_var;
|
||||
|
||||
$derivative_params = trigger_change('get_index_album_derivative_params', ImageStdParams::get_by_type(IMG_THUMB) );
|
||||
$tpl_thumbnails_var_selection = trigger_change('loc_end_index_category_thumbnails', $tpl_thumbnails_var_selection);
|
||||
$template->assign( array(
|
||||
'maxRequests' =>$conf['max_requests'],
|
||||
'category_thumbnails' => $tpl_thumbnails_var_selection,
|
||||
'derivative_params' => $derivative_params,
|
||||
) );
|
||||
|
||||
$template->assign_var_from_handle('CATEGORIES', 'index_category_thumbnails');
|
||||
|
||||
// navigation bar
|
||||
$page['cats_navigation_bar'] = array();
|
||||
if ($page['total_categories'] > $conf['nb_categories_page'])
|
||||
{
|
||||
$page['cats_navigation_bar'] = create_navigation_bar(
|
||||
duplicate_index_url(array(), array('startcat')),
|
||||
$page['total_categories'],
|
||||
$page['startcat'],
|
||||
$conf['nb_categories_page'],
|
||||
true, 'startcat'
|
||||
);
|
||||
}
|
||||
|
||||
$template->assign('cats_navbar', $page['cats_navigation_bar'] );
|
||||
}
|
||||
|
||||
pwg_debug('end include/category_cats.inc.php');
|
||||
?>
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This file is included by the main page to show thumbnails for the default
|
||||
* case
|
||||
*
|
||||
*/
|
||||
|
||||
$pictures = array();
|
||||
|
||||
$selection = array_slice(
|
||||
$page['items'],
|
||||
$page['start'],
|
||||
$page['nb_image_page']
|
||||
);
|
||||
|
||||
$selection = trigger_change('loc_index_thumbnails_selection', $selection);
|
||||
|
||||
if (count($selection) > 0)
|
||||
{
|
||||
$rank_of = array_flip($selection);
|
||||
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.IMAGES_TABLE.'
|
||||
WHERE id IN ('.implode(',', $selection).')
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$row['rank'] = $rank_of[ $row['id'] ];
|
||||
$pictures[] = $row;
|
||||
}
|
||||
|
||||
usort($pictures, 'rank_compare');
|
||||
unset($rank_of);
|
||||
}
|
||||
|
||||
if (count($pictures) > 0)
|
||||
{
|
||||
// define category slideshow url
|
||||
$row = reset($pictures);
|
||||
$page['cat_slideshow_url'] =
|
||||
add_url_params(
|
||||
duplicate_picture_url(
|
||||
array(
|
||||
'image_id' => $row['id'],
|
||||
'image_file' => $row['file']
|
||||
),
|
||||
array('start')
|
||||
),
|
||||
array('slideshow' =>
|
||||
(isset($_GET['slideshow']) ? $_GET['slideshow']
|
||||
: '' ))
|
||||
);
|
||||
|
||||
if ($conf['activate_comments'] and $user['show_nb_comments'])
|
||||
{
|
||||
$query = '
|
||||
SELECT image_id, COUNT(*) AS nb_comments
|
||||
FROM '.COMMENTS_TABLE.'
|
||||
WHERE validated = \'true\'
|
||||
AND image_id IN ('.implode(',', $selection).')
|
||||
GROUP BY image_id
|
||||
;';
|
||||
$nb_comments_of = query2array($query, 'image_id', 'nb_comments');
|
||||
}
|
||||
}
|
||||
|
||||
// template thumbnail initialization
|
||||
$template->set_filenames( array( 'index_thumbnails' => 'thumbnails.tpl',));
|
||||
|
||||
trigger_notify('loc_begin_index_thumbnails', $pictures);
|
||||
$tpl_thumbnails_var = array();
|
||||
|
||||
foreach ($pictures as $row)
|
||||
{
|
||||
// link on picture.php page
|
||||
$url = duplicate_picture_url(
|
||||
array(
|
||||
'image_id' => $row['id'],
|
||||
'image_file' => $row['file']
|
||||
),
|
||||
array('start')
|
||||
);
|
||||
|
||||
if (isset($nb_comments_of))
|
||||
{
|
||||
$row['NB_COMMENTS'] = $row['nb_comments'] = (int)@$nb_comments_of[$row['id']];
|
||||
}
|
||||
|
||||
$name = render_element_name($row);
|
||||
$desc = render_element_description($row, 'main_page_element_description');
|
||||
|
||||
$tpl_var = array_merge( $row, array(
|
||||
'TN_ALT' => htmlspecialchars(strip_tags($name)),
|
||||
'TN_TITLE' => get_thumbnail_title($row, $name, $desc),
|
||||
'URL' => $url,
|
||||
'DESCRIPTION' => $desc,
|
||||
'src_image' => new SrcImage($row),
|
||||
'path_ext' => strtolower(get_extension($row['path'])),
|
||||
'file_ext' => strtolower(get_extension($row['file'])),
|
||||
) );
|
||||
|
||||
if ($conf['index_new_icon'])
|
||||
{
|
||||
$tpl_var['icon_ts'] = get_icon($row['date_available']);
|
||||
}
|
||||
|
||||
if ($user['show_nb_hits'])
|
||||
{
|
||||
$tpl_var['NB_HITS'] = $row['hit'];
|
||||
}
|
||||
|
||||
switch ($page['section'])
|
||||
{
|
||||
case 'best_rated' :
|
||||
{
|
||||
$name = '('.$row['rating_score'].') '.$name;
|
||||
break;
|
||||
}
|
||||
case 'most_visited' :
|
||||
{
|
||||
if ( !$user['show_nb_hits'])
|
||||
{
|
||||
$name = '('.$row['hit'].') '.$name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$tpl_var['NAME'] = $name;
|
||||
$tpl_thumbnails_var[] = $tpl_var;
|
||||
}
|
||||
|
||||
$template->assign( array(
|
||||
'derivative_params' => trigger_change('get_index_derivative_params', ImageStdParams::get_by_type( pwg_get_session_var('index_deriv', IMG_THUMB) ) ),
|
||||
'maxRequests' =>$conf['max_requests'],
|
||||
'SHOW_THUMBNAIL_CAPTION' =>$conf['show_thumbnail_caption'],
|
||||
) );
|
||||
$tpl_thumbnails_var = trigger_change('loc_end_index_thumbnails', $tpl_thumbnails_var, $pictures);
|
||||
$template->assign('thumbnails', $tpl_thumbnails_var);
|
||||
|
||||
$template->assign_var_from_handle('THUMBNAILS', 'index_thumbnails');
|
||||
unset($pictures, $selection, $tpl_thumbnails_var);
|
||||
$template->clear_assign( 'thumbnails' );
|
||||
pwg_debug('end include/category_default.inc.php');
|
||||
?>
|
||||
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
defined('PHPWG_ROOT_PATH') or trigger_error('Hacking attempt!', E_USER_ERROR);
|
||||
|
||||
// determine the initial instant to indicate the generation time of this page
|
||||
$t2 = microtime(true);
|
||||
|
||||
// @set_magic_quotes_runtime(0); // Disable magic_quotes_runtime
|
||||
|
||||
//
|
||||
// addslashes to vars if magic_quotes_gpc is off this is a security
|
||||
// precaution to prevent someone trying to break out of a SQL statement.
|
||||
//
|
||||
// The magic quote feature has been disabled since php 5.4
|
||||
// but function get_magic_quotes_gpc was always replying false.
|
||||
// Since php 8 the function get_magic_quotes_gpc is also removed
|
||||
// but we stil want to sanitize user input variables.
|
||||
if(!function_exists('get_magic_quotes_gpc') or !@get_magic_quotes_gpc() )
|
||||
{
|
||||
function sanitize_mysql_kv(&$v, $k)
|
||||
{
|
||||
$v = addslashes($v);
|
||||
}
|
||||
if( is_array( $_GET ) )
|
||||
{
|
||||
array_walk_recursive( $_GET, 'sanitize_mysql_kv' );
|
||||
}
|
||||
if( is_array( $_POST ) )
|
||||
{
|
||||
array_walk_recursive( $_POST, 'sanitize_mysql_kv' );
|
||||
}
|
||||
if( is_array( $_COOKIE ) )
|
||||
{
|
||||
array_walk_recursive( $_COOKIE, 'sanitize_mysql_kv' );
|
||||
}
|
||||
}
|
||||
if ( !empty($_SERVER["PATH_INFO"]) )
|
||||
{
|
||||
$_SERVER["PATH_INFO"] = addslashes($_SERVER["PATH_INFO"]);
|
||||
}
|
||||
|
||||
//
|
||||
// Define some basic configuration arrays this also prevents malicious
|
||||
// rewriting of language and otherarray values via URI params
|
||||
//
|
||||
$conf = array();
|
||||
$page = array(
|
||||
'infos' => array(),
|
||||
'errors' => array(),
|
||||
'warnings' => array(),
|
||||
'messages' => array(),
|
||||
'body_classes' => array(),
|
||||
'body_data' => array(),
|
||||
);
|
||||
$user = array();
|
||||
$lang = array();
|
||||
$header_msgs = array();
|
||||
$header_notes = array();
|
||||
$filter = array();
|
||||
|
||||
foreach(
|
||||
array(
|
||||
'gzopen',
|
||||
'str_starts_with'
|
||||
) as $func)
|
||||
{
|
||||
if (!function_exists($func))
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH . 'include/php_compat/'.$func.'.php');
|
||||
}
|
||||
}
|
||||
|
||||
include(PHPWG_ROOT_PATH . 'include/config_default.inc.php');
|
||||
@include(PHPWG_ROOT_PATH. 'local/config/config.inc.php');
|
||||
|
||||
defined('PWG_LOCAL_DIR') or define('PWG_LOCAL_DIR', 'local/');
|
||||
|
||||
@include(PHPWG_ROOT_PATH.PWG_LOCAL_DIR .'config/database.inc.php');
|
||||
if (!defined('PHPWG_INSTALLED'))
|
||||
{
|
||||
header('Location: install.php');
|
||||
exit;
|
||||
}
|
||||
include(PHPWG_ROOT_PATH .'include/dblayer/functions_'.$conf['dblayer'].'.inc.php');
|
||||
|
||||
if(isset($conf['show_php_errors']) && !empty($conf['show_php_errors']))
|
||||
{
|
||||
@ini_set('error_reporting', $conf['show_php_errors']);
|
||||
if($conf['show_php_errors_on_frontend'])
|
||||
{
|
||||
@ini_set('display_errors', true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf['session_gc_probability'] > 0)
|
||||
{
|
||||
@ini_set('session.gc_divisor', 100);
|
||||
@ini_set('session.gc_probability', min((int)$conf['session_gc_probability'], 100));
|
||||
}
|
||||
|
||||
include(PHPWG_ROOT_PATH . 'include/constants.php');
|
||||
include(PHPWG_ROOT_PATH . 'include/functions.inc.php');
|
||||
include(PHPWG_ROOT_PATH . 'include/template.class.php');
|
||||
include(PHPWG_ROOT_PATH . 'include/cache.class.php');
|
||||
include(PHPWG_ROOT_PATH . 'include/Logger.class.php');
|
||||
|
||||
$page['execution_uuid'] = generate_key(10);
|
||||
|
||||
$persistent_cache = new PersistentFileCache();
|
||||
|
||||
// Database connection
|
||||
try
|
||||
{
|
||||
pwg_db_connect($conf['db_host'], $conf['db_user'],
|
||||
$conf['db_password'], $conf['db_base']);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
my_error(l10n($e->getMessage()), true);
|
||||
}
|
||||
|
||||
pwg_db_check_charset();
|
||||
|
||||
// in Piwigo 15, configuration setting webmaster_id is moved from config files
|
||||
// to database. It may be undefined at some point, with Piwigo 15+ scripts and
|
||||
// a Piwigo 14 database schema not upgraded yet. Let's avoid any problem.
|
||||
$conf['webmaster_id'] = $conf['webmaster_id'] ?? 1;
|
||||
|
||||
load_conf_from_db();
|
||||
|
||||
$logger = new Logger(array(
|
||||
'directory' => PHPWG_ROOT_PATH . $conf['data_location'] . $conf['log_dir'],
|
||||
'severity' => $conf['log_level'],
|
||||
// we use an hashed filename to prevent direct file access, and we salt with
|
||||
// the db_password instead of secret_key because the log must be usable in i.php
|
||||
// (secret_key is in the database)
|
||||
'filename' => 'log_' . date('Y-m-d') . '_' . sha1(date('Y-m-d') . $conf['db_password']) . '.txt',
|
||||
'globPattern' => 'log_*.txt',
|
||||
'archiveDays' => $conf['log_archive_days'],
|
||||
));
|
||||
|
||||
if (!$conf['check_upgrade_feed'])
|
||||
{
|
||||
if (!isset($conf['piwigo_db_version']) or $conf['piwigo_db_version'] != get_branch_from_version(PHPWG_VERSION))
|
||||
{
|
||||
redirect(get_root_url().'upgrade.php');
|
||||
}
|
||||
}
|
||||
|
||||
ImageStdParams::load_from_db();
|
||||
|
||||
session_start();
|
||||
load_plugins();
|
||||
|
||||
if (!isset($conf['piwigo_installed_version']))
|
||||
{
|
||||
conf_update_param('piwigo_installed_version', PHPWG_VERSION);
|
||||
}
|
||||
elseif ($conf['piwigo_installed_version'] != PHPWG_VERSION)
|
||||
{
|
||||
// Piwigo has been updated "from filesystem" and not "from the administration UI". We mark it as an autoupdate in the system activities log
|
||||
pwg_activity('system', ACTIVITY_SYSTEM_CORE, 'autoupdate', array('from_version'=>$conf['piwigo_installed_version'], 'to_version'=>PHPWG_VERSION));
|
||||
conf_update_param('piwigo_installed_version', PHPWG_VERSION);
|
||||
}
|
||||
|
||||
//Check if last major update conf is set if not set it
|
||||
if (!isset($conf['last_major_update']))
|
||||
{
|
||||
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW();'));
|
||||
conf_update_param('last_major_update', $dbnow, true);
|
||||
}
|
||||
|
||||
// 2022-02-25 due to escape on "rank" (becoming a mysql keyword in version 8), the $conf['order_by'] might
|
||||
// use a "rank", even if admin/configuration.php should have removed it. We must remove it.
|
||||
// TODO remove this data update as soon as 2025 arrives
|
||||
if (preg_match('/(, )?`rank` ASC/', $conf['order_by']))
|
||||
{
|
||||
$order_by = preg_replace('/(, )?`rank` ASC/', '', $conf['order_by']);
|
||||
if ('ORDER BY ' == $order_by)
|
||||
{
|
||||
$order_by = 'ORDER BY id ASC';
|
||||
}
|
||||
conf_update_param('order_by', $order_by, true);
|
||||
}
|
||||
|
||||
// users can have defined a custom order pattern, incompatible with GUI form
|
||||
if (isset($conf['order_by_custom']))
|
||||
{
|
||||
$conf['order_by'] = $conf['order_by_custom'];
|
||||
}
|
||||
if (isset($conf['order_by_inside_category_custom']))
|
||||
{
|
||||
$conf['order_by_inside_category'] = $conf['order_by_inside_category_custom'];
|
||||
}
|
||||
|
||||
check_lounge();
|
||||
|
||||
include(PHPWG_ROOT_PATH.'include/user.inc.php');
|
||||
|
||||
if (in_array( substr($user['language'],0,2), array('fr','it','de','es','pl','ru','nl','tr','da') ) )
|
||||
{
|
||||
define('PHPWG_DOMAIN', substr($user['language'],0,2).'.piwigo.org');
|
||||
}
|
||||
elseif ('zh_CN' == $user['language']) {
|
||||
define('PHPWG_DOMAIN', 'cn.piwigo.org');
|
||||
}
|
||||
elseif ('pt_BR' == $user['language']) {
|
||||
define('PHPWG_DOMAIN', 'br.piwigo.org');
|
||||
}
|
||||
else {
|
||||
define('PHPWG_DOMAIN', 'piwigo.org');
|
||||
}
|
||||
define('PHPWG_URL', 'https://'.PHPWG_DOMAIN);
|
||||
|
||||
if(isset($conf['alternative_pem_url']) and $conf['alternative_pem_url']!='')
|
||||
{
|
||||
define('PEM_URL', $conf['alternative_pem_url']);
|
||||
}
|
||||
else
|
||||
{
|
||||
define('PEM_URL', 'https://'.PHPWG_DOMAIN.'/ext');
|
||||
}
|
||||
|
||||
// language files
|
||||
load_language('common.lang');
|
||||
if ( is_admin() || (defined('IN_ADMIN') and IN_ADMIN) )
|
||||
{
|
||||
load_language('admin.lang');
|
||||
// Add language for temporary strings for new popup, from piwigo 15
|
||||
load_language('whats_new_'.get_branch_from_version(PHPWG_VERSION).'.lang');
|
||||
}
|
||||
trigger_notify('loading_lang');
|
||||
load_language('lang', PHPWG_ROOT_PATH.PWG_LOCAL_DIR, array('no_fallback'=>true, 'local'=>true) );
|
||||
|
||||
// only now we can set the localized username of the guest user (and not in
|
||||
// include/user.inc.php)
|
||||
if (is_a_guest())
|
||||
{
|
||||
$user['username'] = l10n('guest');
|
||||
}
|
||||
|
||||
// in case an auth key was provided and is no longer valid, we must wait to
|
||||
// be here, with language loaded, to prepare the message
|
||||
if (isset($page['auth_key_invalid']) and $page['auth_key_invalid'])
|
||||
{
|
||||
$page['errors'][] =
|
||||
l10n('Your authentication key is no longer valid.')
|
||||
.sprintf(' <a href="%s">%s</a>', get_root_url().'identification.php', l10n('Login'))
|
||||
;
|
||||
}
|
||||
|
||||
// check if we need to notified user about api_key expiration
|
||||
if (isset($page['notify_api_key_expiration']) and is_array($page['notify_api_key_expiration']))
|
||||
{
|
||||
$is_mail_send = notification_api_key_expiration(
|
||||
$user['username'],
|
||||
$user['email'],
|
||||
$page['notify_api_key_expiration']['days_left']
|
||||
);
|
||||
|
||||
if ($is_mail_send)
|
||||
{
|
||||
single_update(
|
||||
USER_AUTH_KEYS_TABLE,
|
||||
array('last_notified_on' => $page['notify_api_key_expiration']['dbnow']),
|
||||
array(
|
||||
'user_id' => $user['id'],
|
||||
'auth_key' => $page['notify_api_key_expiration']['auth_key']
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
unset($page['notify_api_key_expiration']);
|
||||
}
|
||||
|
||||
// template instance
|
||||
if (defined('IN_ADMIN') and IN_ADMIN )
|
||||
{// Admin template
|
||||
$template = new Template(PHPWG_ROOT_PATH.'admin/themes', userprefs_get_param('admin_theme', 'clear'));
|
||||
}
|
||||
else
|
||||
{ // Classic template
|
||||
$theme = $user['theme'];
|
||||
if (script_basename() != 'ws' and mobile_theme())
|
||||
{
|
||||
$theme = $conf['mobile_theme'];
|
||||
}
|
||||
$template = new Template(PHPWG_ROOT_PATH.'themes', $theme );
|
||||
}
|
||||
|
||||
if ( !isset($conf['no_photo_yet']) )
|
||||
{
|
||||
include(PHPWG_ROOT_PATH.'include/no_photo_yet.inc.php');
|
||||
}
|
||||
|
||||
if (isset($user['internal_status']['guest_must_be_guest'])
|
||||
and
|
||||
$user['internal_status']['guest_must_be_guest'] === true)
|
||||
{
|
||||
$header_msgs[] = l10n('Bad status for user "guest", using default status. Please notify the webmaster.');
|
||||
}
|
||||
|
||||
if ($conf['gallery_locked'])
|
||||
{
|
||||
$header_msgs[] = l10n('The gallery is locked for maintenance. Please, come back later.');
|
||||
|
||||
if ( script_basename() != 'identification' and !is_admin() )
|
||||
{
|
||||
set_status_header(503, 'Service Unavailable');
|
||||
@header('Retry-After: 900');
|
||||
header('Content-Type: text/html; charset='.get_pwg_charset());
|
||||
echo '<a href="'.get_absolute_root_url(false).'identification.php">'.l10n('The gallery is locked for maintenance. Please, come back later.').'</a>';
|
||||
echo str_repeat( ' ', 512); //IE6 doesn't error output if below a size
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf['check_upgrade_feed'])
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'admin/include/functions_upgrade.php');
|
||||
if (check_upgrade_feed())
|
||||
{
|
||||
$header_msgs[] = 'Some database upgrades are missing, '
|
||||
.'<a href="'.get_absolute_root_url(false).'upgrade_feed.php">upgrade now</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (count($header_msgs) > 0)
|
||||
{
|
||||
$template->assign('header_msgs', $header_msgs);
|
||||
$header_msgs=array();
|
||||
}
|
||||
|
||||
if (!empty($conf['filter_pages']) and get_filter_page_value('used'))
|
||||
{
|
||||
include(PHPWG_ROOT_PATH.'include/filter.inc.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter['enabled'] = false;
|
||||
}
|
||||
|
||||
if (isset($conf['header_notes']))
|
||||
{
|
||||
$header_notes = array_merge($header_notes, $conf['header_notes']);
|
||||
}
|
||||
|
||||
// default event handlers
|
||||
add_event_handler('render_category_literal_description', 'render_category_literal_description');
|
||||
if ( !$conf['allow_html_descriptions'] )
|
||||
{
|
||||
add_event_handler('render_category_description', 'pwg_nl2br');
|
||||
}
|
||||
add_event_handler('render_comment_content', 'render_comment_content');
|
||||
add_event_handler('render_comment_author', 'strip_tags');
|
||||
add_event_handler('render_tag_url', 'str2url');
|
||||
add_event_handler('blockmanager_register_blocks', 'register_default_menubar_blocks', EVENT_HANDLER_PRIORITY_NEUTRAL-1);
|
||||
if ( !empty($conf['original_url_protection']) )
|
||||
{
|
||||
add_event_handler('get_element_url', 'get_element_url_protection_handler');
|
||||
add_event_handler('get_src_image_url', 'get_src_image_url_protection_handler');
|
||||
}
|
||||
trigger_notify('init');
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
// Default settings
|
||||
define('PHPWG_VERSION', '16.3.0');
|
||||
define('PHPWG_DEFAULT_LANGUAGE', 'en_UK');
|
||||
|
||||
// this constant is only used in the upgrade process, the true default theme
|
||||
// is the theme of user "guest", which is initialized with column user_infos.theme
|
||||
// default value (see file install/piwigo_structure-mysql.sql)
|
||||
define('PHPWG_DEFAULT_TEMPLATE', 'modus');
|
||||
|
||||
define('PHPWG_THEMES_PATH', $conf['themes_dir'].'/');
|
||||
defined('PWG_COMBINED_DIR') or define('PWG_COMBINED_DIR', $conf['data_location'].'combined/');
|
||||
defined('PWG_DERIVATIVE_DIR') or define('PWG_DERIVATIVE_DIR', $conf['data_location'].'i/');
|
||||
|
||||
// Required versions
|
||||
define('REQUIRED_PHP_VERSION', '7.4.0');
|
||||
|
||||
// Access codes
|
||||
define('ACCESS_FREE', 0);
|
||||
define('ACCESS_GUEST', 1);
|
||||
define('ACCESS_CLASSIC', 2);
|
||||
define('ACCESS_ADMINISTRATOR', 3);
|
||||
define('ACCESS_WEBMASTER', 4);
|
||||
define('ACCESS_CLOSED', 5);
|
||||
|
||||
// System activities
|
||||
define('ACTIVITY_SYSTEM_CORE', 1);
|
||||
define('ACTIVITY_SYSTEM_PLUGIN', 2);
|
||||
define('ACTIVITY_SYSTEM_THEME', 3);
|
||||
|
||||
// Sanity checks
|
||||
define('PATTERN_ID', '/^\d+$/');
|
||||
define('PATTERN_ORDER', '/^(rand(om)?|[a-z_]+(\s+(asc|desc))?)(\s*,\s*(rand(om)?|[a-z_]+(\s+(asc|desc))?))*$/i');
|
||||
|
||||
// Table names
|
||||
if (!defined('CATEGORIES_TABLE'))
|
||||
define('CATEGORIES_TABLE', $prefixeTable.'categories');
|
||||
if (!defined('COMMENTS_TABLE'))
|
||||
define('COMMENTS_TABLE', $prefixeTable.'comments');
|
||||
if (!defined('CONFIG_TABLE'))
|
||||
define('CONFIG_TABLE', $prefixeTable.'config');
|
||||
if (!defined('FAVORITES_TABLE'))
|
||||
define('FAVORITES_TABLE', $prefixeTable.'favorites');
|
||||
if (!defined('GROUP_ACCESS_TABLE'))
|
||||
define('GROUP_ACCESS_TABLE', $prefixeTable.'group_access');
|
||||
if (!defined('GROUPS_TABLE'))
|
||||
define('GROUPS_TABLE', $prefixeTable.'groups');
|
||||
if (!defined('HISTORY_TABLE'))
|
||||
define('HISTORY_TABLE', $prefixeTable.'history');
|
||||
if (!defined('HISTORY_SUMMARY_TABLE'))
|
||||
define('HISTORY_SUMMARY_TABLE', $prefixeTable.'history_summary');
|
||||
if (!defined('IMAGE_CATEGORY_TABLE'))
|
||||
define('IMAGE_CATEGORY_TABLE', $prefixeTable.'image_category');
|
||||
if (!defined('IMAGES_TABLE'))
|
||||
define('IMAGES_TABLE', $prefixeTable.'images');
|
||||
if (!defined('SESSIONS_TABLE'))
|
||||
define('SESSIONS_TABLE', $prefixeTable.'sessions');
|
||||
if (!defined('SITES_TABLE'))
|
||||
define('SITES_TABLE', $prefixeTable.'sites');
|
||||
if (!defined('USER_ACCESS_TABLE'))
|
||||
define('USER_ACCESS_TABLE', $prefixeTable.'user_access');
|
||||
if (!defined('USER_GROUP_TABLE'))
|
||||
define('USER_GROUP_TABLE', $prefixeTable.'user_group');
|
||||
if (!defined('USERS_TABLE'))
|
||||
define('USERS_TABLE', isset($conf['users_table']) ? $conf['users_table'] : $prefixeTable.'users' );
|
||||
if (!defined('USER_INFOS_TABLE'))
|
||||
define('USER_INFOS_TABLE', $prefixeTable.'user_infos');
|
||||
if (!defined('USER_FEED_TABLE'))
|
||||
define('USER_FEED_TABLE', $prefixeTable.'user_feed');
|
||||
if (!defined('RATE_TABLE'))
|
||||
define('RATE_TABLE', $prefixeTable.'rate');
|
||||
if (!defined('USER_AUTH_KEYS_TABLE'))
|
||||
define('USER_AUTH_KEYS_TABLE', $prefixeTable.'user_auth_keys');
|
||||
if (!defined('USER_CACHE_TABLE'))
|
||||
define('USER_CACHE_TABLE', $prefixeTable.'user_cache');
|
||||
if (!defined('USER_CACHE_CATEGORIES_TABLE'))
|
||||
define('USER_CACHE_CATEGORIES_TABLE', $prefixeTable.'user_cache_categories');
|
||||
if (!defined('CADDIE_TABLE'))
|
||||
define('CADDIE_TABLE', $prefixeTable.'caddie');
|
||||
if (!defined('UPGRADE_TABLE'))
|
||||
define('UPGRADE_TABLE', $prefixeTable.'upgrade');
|
||||
if (!defined('SEARCH_TABLE'))
|
||||
define('SEARCH_TABLE', $prefixeTable.'search');
|
||||
if (!defined('USER_MAIL_NOTIFICATION_TABLE'))
|
||||
define('USER_MAIL_NOTIFICATION_TABLE', $prefixeTable.'user_mail_notification');
|
||||
if (!defined('TAGS_TABLE'))
|
||||
define('TAGS_TABLE', $prefixeTable.'tags');
|
||||
if (!defined('IMAGE_TAG_TABLE'))
|
||||
define('IMAGE_TAG_TABLE', $prefixeTable.'image_tag');
|
||||
if (!defined('PLUGINS_TABLE'))
|
||||
define('PLUGINS_TABLE', $prefixeTable.'plugins');
|
||||
if (!defined('OLD_PERMALINKS_TABLE'))
|
||||
define('OLD_PERMALINKS_TABLE', $prefixeTable.'old_permalinks');
|
||||
if (!defined('THEMES_TABLE'))
|
||||
define('THEMES_TABLE', $prefixeTable.'themes');
|
||||
if (!defined('LANGUAGES_TABLE'))
|
||||
define('LANGUAGES_TABLE', $prefixeTable.'languages');
|
||||
if (!defined('IMAGE_FORMAT_TABLE'))
|
||||
define('IMAGE_FORMAT_TABLE', $prefixeTable.'image_format');
|
||||
if (!defined('ACTIVITY_TABLE'))
|
||||
define('ACTIVITY_TABLE', $prefixeTable.'activity');
|
||||
if (!defined('LOUNGE_TABLE'))
|
||||
define('LOUNGE_TABLE', $prefixeTable.'lounge');
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,818 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
define('DB_ENGINE', 'MySQL');
|
||||
define('REQUIRED_MYSQL_VERSION', '5.0.0');
|
||||
|
||||
define('DB_REGEX_OPERATOR', 'REGEXP');
|
||||
define('DB_RANDOM_FUNCTION', 'RAND');
|
||||
|
||||
/**
|
||||
*
|
||||
* simple functions
|
||||
*
|
||||
*/
|
||||
|
||||
function pwg_db_connect($host, $user, $password, $database)
|
||||
{
|
||||
$link = @mysql_connect($host, $user, $password);
|
||||
if (!$link)
|
||||
{
|
||||
throw new Exception("Can't connect to server");
|
||||
}
|
||||
if (!mysql_select_db($database, $link))
|
||||
{
|
||||
throw new Exception('Connection to server succeed, but it was impossible to connect to database');
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_check_charset()
|
||||
{
|
||||
$db_charset = 'utf8';
|
||||
if (defined('DB_CHARSET') and DB_CHARSET != '')
|
||||
{
|
||||
$db_charset = DB_CHARSET;
|
||||
}
|
||||
mysql_set_charset($db_charset);
|
||||
}
|
||||
|
||||
function pwg_db_check_version()
|
||||
{
|
||||
$current_mysql = pwg_get_db_version();
|
||||
if (version_compare($current_mysql, REQUIRED_MYSQL_VERSION, '<'))
|
||||
{
|
||||
fatal_error(
|
||||
sprintf(
|
||||
'your MySQL version is too old, you have "%s" and you need at least "%s"',
|
||||
$current_mysql,
|
||||
REQUIRED_MYSQL_VERSION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_get_db_version()
|
||||
{
|
||||
return mysql_get_server_info();
|
||||
}
|
||||
|
||||
function pwg_query($query)
|
||||
{
|
||||
global $conf,$page,$debug,$t2;
|
||||
|
||||
$start = microtime(true);
|
||||
($result = mysql_query($query)) or my_error($query, $conf['die_on_sql_error']);
|
||||
|
||||
$time = microtime(true) - $start;
|
||||
|
||||
if (!isset($page['count_queries']))
|
||||
{
|
||||
$page['count_queries'] = 0;
|
||||
$page['queries_time'] = 0;
|
||||
}
|
||||
|
||||
$page['count_queries']++;
|
||||
$page['queries_time']+= $time;
|
||||
|
||||
if ($conf['show_queries'])
|
||||
{
|
||||
$output = '';
|
||||
$output.= '<pre>['.$page['count_queries'].'] ';
|
||||
$output.= "\n".$query;
|
||||
$output.= "\n".'(this query time : ';
|
||||
$output.= '<b>'.number_format($time, 3, '.', ' ').' s)</b>';
|
||||
$output.= "\n".'(total SQL time : ';
|
||||
$output.= number_format($page['queries_time'], 3, '.', ' ').' s)';
|
||||
$output.= "\n".'(total time : ';
|
||||
$output.= number_format( ($time+$start-$t2), 3, '.', ' ').' s)';
|
||||
if ( $result!=null and preg_match('/\s*SELECT\s+/i',$query) )
|
||||
{
|
||||
$output.= "\n".'(num rows : ';
|
||||
$output.= mysql_num_rows($result).' )';
|
||||
}
|
||||
elseif ( $result!=null
|
||||
and preg_match('/\s*INSERT|UPDATE|REPLACE|DELETE\s+/i',$query) )
|
||||
{
|
||||
$output.= "\n".'(affected rows : ';
|
||||
$output.= mysql_affected_rows().' )';
|
||||
}
|
||||
$output.= "</pre>\n";
|
||||
|
||||
$debug .= $output;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function pwg_db_nextval($column, $table)
|
||||
{
|
||||
$query = '
|
||||
SELECT IF(MAX('.$column.')+1 IS NULL, 1, MAX('.$column.')+1)
|
||||
FROM '.$table;
|
||||
list($next) = pwg_db_fetch_row(pwg_query($query));
|
||||
|
||||
return $next;
|
||||
}
|
||||
|
||||
function pwg_db_changes()
|
||||
{
|
||||
return mysql_affected_rows();
|
||||
}
|
||||
|
||||
function pwg_db_num_rows($result)
|
||||
{
|
||||
return mysql_num_rows($result);
|
||||
}
|
||||
|
||||
function pwg_db_fetch_array($result)
|
||||
{
|
||||
return mysql_fetch_array($result);
|
||||
}
|
||||
|
||||
function pwg_db_fetch_assoc($result)
|
||||
{
|
||||
return mysql_fetch_assoc($result);
|
||||
}
|
||||
|
||||
function pwg_db_fetch_row($result)
|
||||
{
|
||||
return mysql_fetch_row($result);
|
||||
}
|
||||
|
||||
function pwg_db_fetch_object($result)
|
||||
{
|
||||
return mysql_fetch_object($result);
|
||||
}
|
||||
|
||||
function pwg_db_free_result($result)
|
||||
{
|
||||
return mysql_free_result($result);
|
||||
}
|
||||
|
||||
function pwg_db_real_escape_string($s)
|
||||
{
|
||||
return mysql_real_escape_string($s);
|
||||
}
|
||||
|
||||
function pwg_db_insert_id()
|
||||
{
|
||||
return mysql_insert_id();
|
||||
}
|
||||
|
||||
function pwg_db_errno()
|
||||
{
|
||||
return mysql_errno();
|
||||
}
|
||||
|
||||
function pwg_db_error()
|
||||
{
|
||||
return mysql_error();
|
||||
}
|
||||
|
||||
function pwg_db_close()
|
||||
{
|
||||
return mysql_close();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* complex functions
|
||||
*
|
||||
*/
|
||||
|
||||
define('MASS_UPDATES_SKIP_EMPTY', 1);
|
||||
/**
|
||||
* updates multiple lines in a table
|
||||
*
|
||||
* @param string table_name
|
||||
* @param array dbfields
|
||||
* @param array datas
|
||||
* @param int flags - if MASS_UPDATES_SKIP_EMPTY - empty values do not overwrite existing ones
|
||||
* @return void
|
||||
*/
|
||||
function mass_updates($tablename, $dbfields, $datas, $flags=0)
|
||||
{
|
||||
if (count($datas) == 0)
|
||||
return;
|
||||
|
||||
// depending on the MySQL version, we use the multi table update or N update queries
|
||||
if (count($datas) < 10)
|
||||
{
|
||||
foreach ($datas as $data)
|
||||
{
|
||||
$query = '
|
||||
UPDATE '.$tablename.'
|
||||
SET ';
|
||||
$is_first = true;
|
||||
foreach ($dbfields['update'] as $key)
|
||||
{
|
||||
$separator = $is_first ? '' : ",\n ";
|
||||
|
||||
if (isset($data[$key]) and $data[$key] != '')
|
||||
{
|
||||
$query.= $separator.$key.' = \''.$data[$key].'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $flags & MASS_UPDATES_SKIP_EMPTY )
|
||||
continue; // next field
|
||||
$query.= "$separator$key = NULL";
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
if (!$is_first)
|
||||
{// only if one field at least updated
|
||||
$query.= '
|
||||
WHERE ';
|
||||
$is_first = true;
|
||||
foreach ($dbfields['primary'] as $key)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query.= ' AND ';
|
||||
}
|
||||
if ( isset($data[$key]) )
|
||||
{
|
||||
$query.= $key.' = \''.$data[$key].'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= $key.' IS NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
pwg_query($query);
|
||||
}
|
||||
} // foreach update
|
||||
} // if mysql_ver or count<X
|
||||
else
|
||||
{
|
||||
// creation of the temporary table
|
||||
$query = '
|
||||
SHOW FULL COLUMNS FROM '.$tablename;
|
||||
$result = pwg_query($query);
|
||||
$columns = array();
|
||||
$all_fields = array_merge($dbfields['primary'], $dbfields['update']);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if (in_array($row['Field'], $all_fields))
|
||||
{
|
||||
$column = $row['Field'];
|
||||
$column.= ' '.$row['Type'];
|
||||
|
||||
$nullable = true;
|
||||
if (!isset($row['Null']) or $row['Null'] == '' or $row['Null']=='NO')
|
||||
{
|
||||
$column.= ' NOT NULL';
|
||||
$nullable = false;
|
||||
}
|
||||
if (isset($row['Default']))
|
||||
{
|
||||
$column.= " default '".$row['Default']."'";
|
||||
}
|
||||
elseif ($nullable)
|
||||
{
|
||||
$column.= " default NULL";
|
||||
}
|
||||
if (isset($row['Collation']) and $row['Collation'] != 'NULL')
|
||||
{
|
||||
$column.= " collate '".$row['Collation']."'";
|
||||
}
|
||||
$columns[] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
$temporary_tablename = $tablename.'_'.micro_seconds();
|
||||
|
||||
$query = '
|
||||
CREATE TABLE '.$temporary_tablename.'
|
||||
(
|
||||
'.implode(",\n ", $columns).',
|
||||
UNIQUE KEY the_key ('.implode(',', $dbfields['primary']).')
|
||||
)';
|
||||
|
||||
pwg_query($query);
|
||||
mass_inserts($temporary_tablename, $all_fields, $datas);
|
||||
if ( $flags & MASS_UPDATES_SKIP_EMPTY )
|
||||
$func_set = create_function('$s', 'return "t1.$s = IFNULL(t2.$s, t1.$s)";');
|
||||
else
|
||||
$func_set = create_function('$s', 'return "t1.$s = t2.$s";');
|
||||
|
||||
// update of images table by joining with temporary table
|
||||
$query = '
|
||||
UPDATE '.$tablename.' AS t1, '.$temporary_tablename.' AS t2
|
||||
SET '.
|
||||
implode(
|
||||
"\n , ",
|
||||
array_map($func_set,$dbfields['update'])
|
||||
).'
|
||||
WHERE '.
|
||||
implode(
|
||||
"\n AND ",
|
||||
array_map(
|
||||
create_function('$s', 'return "t1.$s = t2.$s";'),
|
||||
$dbfields['primary']
|
||||
)
|
||||
);
|
||||
pwg_query($query);
|
||||
$query = '
|
||||
DROP TABLE '.$temporary_tablename;
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates one line in a table
|
||||
*
|
||||
* @param string table_name
|
||||
* @param array set_fields
|
||||
* @param array where_fields
|
||||
* @param int flags - if MASS_UPDATES_SKIP_EMPTY - empty values do not overwrite existing ones
|
||||
* @return void
|
||||
*/
|
||||
function single_update($tablename, $set_fields, $where_fields, $flags=0)
|
||||
{
|
||||
if (count($set_fields) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$query = '
|
||||
UPDATE '.$tablename.'
|
||||
SET ';
|
||||
$is_first = true;
|
||||
foreach ($set_fields as $key => $value)
|
||||
{
|
||||
$separator = $is_first ? '' : ",\n ";
|
||||
|
||||
if (isset($value) and $value !== '')
|
||||
{
|
||||
$query.= $separator.$key.' = \''.$value.'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $flags & MASS_UPDATES_SKIP_EMPTY )
|
||||
continue; // next field
|
||||
$query.= "$separator$key = NULL";
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
if (!$is_first)
|
||||
{// only if one field at least updated
|
||||
$query.= '
|
||||
WHERE ';
|
||||
$is_first = true;
|
||||
foreach ($where_fields as $key => $value)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query.= ' AND ';
|
||||
}
|
||||
if ( isset($value) )
|
||||
{
|
||||
$query.= $key.' = \''.$value.'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= $key.' IS NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* inserts multiple lines in a table
|
||||
*
|
||||
* @param string table_name
|
||||
* @param array dbfields
|
||||
* @param array inserts
|
||||
* @return void
|
||||
*/
|
||||
function mass_inserts($table_name, $dbfields, $datas, $options=array())
|
||||
{
|
||||
$ignore = '';
|
||||
if (isset($options['ignore']) and $options['ignore'])
|
||||
{
|
||||
$ignore = 'IGNORE';
|
||||
}
|
||||
|
||||
if (count($datas) != 0)
|
||||
{
|
||||
$first = true;
|
||||
|
||||
$query = 'SHOW VARIABLES LIKE \'max_allowed_packet\'';
|
||||
list(, $packet_size) = pwg_db_fetch_row(pwg_query($query));
|
||||
$packet_size = $packet_size - 2000; // The last list of values MUST not exceed 2000 character*/
|
||||
$query = '';
|
||||
|
||||
foreach ($datas as $insert)
|
||||
{
|
||||
if (strlen($query) >= $packet_size)
|
||||
{
|
||||
pwg_query($query);
|
||||
$first = true;
|
||||
}
|
||||
|
||||
if ($first)
|
||||
{
|
||||
$query = '
|
||||
INSERT '.$ignore.' INTO '.$table_name.'
|
||||
('.implode(',', $dbfields).')
|
||||
VALUES';
|
||||
$first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= '
|
||||
, ';
|
||||
}
|
||||
|
||||
$query .= '(';
|
||||
foreach ($dbfields as $field_id => $dbfield)
|
||||
{
|
||||
if ($field_id > 0)
|
||||
{
|
||||
$query .= ',';
|
||||
}
|
||||
|
||||
if (!isset($insert[$dbfield]) or $insert[$dbfield] === '')
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= "'".$insert[$dbfield]."'";
|
||||
}
|
||||
}
|
||||
$query .= ')';
|
||||
}
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts one line in a table
|
||||
*
|
||||
* @param string table_name
|
||||
* @param array dbfields
|
||||
* @param array insert
|
||||
* @return void
|
||||
*/
|
||||
function single_insert($table_name, $data)
|
||||
{
|
||||
if (count($data) != 0)
|
||||
{
|
||||
$query = '
|
||||
INSERT INTO '.$table_name.'
|
||||
('.implode(',', array_keys($data)).')
|
||||
VALUES';
|
||||
|
||||
$query .= '(';
|
||||
$is_first = true;
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query .= ',';
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
if ($value === '' || is_null($value))
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= "'".$value."'";
|
||||
}
|
||||
}
|
||||
$query .= ')';
|
||||
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do maintenance on all PWG tables
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
function do_maintenance_all_tables()
|
||||
{
|
||||
global $prefixeTable, $page;
|
||||
|
||||
$all_tables = array();
|
||||
|
||||
// List all tables
|
||||
$query = 'SHOW TABLES LIKE \''.$prefixeTable.'%\'';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_row($result))
|
||||
{
|
||||
$all_tables[] = $row[0];
|
||||
}
|
||||
|
||||
// Repair all tables
|
||||
$query = 'REPAIR TABLE '.implode(', ', $all_tables);
|
||||
$mysql_rc = pwg_query($query);
|
||||
|
||||
// Re-Order all tables
|
||||
foreach ($all_tables as $table_name)
|
||||
{
|
||||
$all_primary_key = array();
|
||||
|
||||
$query = 'DESC '.$table_name.';';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if ($row['Key'] == 'PRI')
|
||||
{
|
||||
$all_primary_key[] = $row['Field'];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($all_primary_key) != 0)
|
||||
{
|
||||
$query = 'ALTER TABLE '.$table_name.' ORDER BY '.implode(', ', $all_primary_key).';';
|
||||
$mysql_rc = $mysql_rc && pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize all tables
|
||||
$query = 'OPTIMIZE TABLE '.implode(', ', $all_tables);
|
||||
$mysql_rc = $mysql_rc && pwg_query($query);
|
||||
if ($mysql_rc)
|
||||
{
|
||||
$page['infos'][] = l10n('All optimizations have been successfully completed.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['errors'][] = l10n('Optimizations have been completed with some errors.');
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_concat($array)
|
||||
{
|
||||
$string = implode(',', $array);
|
||||
return 'CONCAT('. $string.')';
|
||||
}
|
||||
|
||||
function pwg_db_concat_ws($array, $separator)
|
||||
{
|
||||
$string = implode(',', $array);
|
||||
return 'CONCAT_WS(\''.$separator.'\','. $string.')';
|
||||
}
|
||||
|
||||
function pwg_db_cast_to_text($string)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array containing the possible values of an enum field
|
||||
*
|
||||
* @param string tablename
|
||||
* @param string fieldname
|
||||
*/
|
||||
function get_enums($table, $field)
|
||||
{
|
||||
// retrieving the properties of the table. Each line represents a field :
|
||||
// columns are 'Field', 'Type'
|
||||
$result = pwg_query('desc '.$table);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
// we are only interested in the the field given in parameter for the
|
||||
// function
|
||||
if ($row['Field'] == $field)
|
||||
{
|
||||
// retrieving possible values of the enum field
|
||||
// enum('blue','green','black')
|
||||
$options = explode(',', substr($row['Type'], 5, -1));
|
||||
foreach ($options as $i => $option)
|
||||
{
|
||||
$options[$i] = str_replace("'", '',$option);
|
||||
}
|
||||
}
|
||||
}
|
||||
pwg_db_free_result($result);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Smartly checks if a variable is equivalent to true or false
|
||||
*
|
||||
* @param mixed input
|
||||
* @return bool
|
||||
*/
|
||||
function get_boolean($input)
|
||||
{
|
||||
if ('false' === strtolower($input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)$input;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns boolean string 'true' or 'false' if the given var is boolean
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return mixed
|
||||
*/
|
||||
function boolean_to_string($var)
|
||||
{
|
||||
if (is_bool($var))
|
||||
{
|
||||
return $var ? 'true' : 'false';
|
||||
}
|
||||
else
|
||||
{
|
||||
return $var;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* interval and date functions
|
||||
*
|
||||
*/
|
||||
|
||||
function pwg_db_get_recent_period_expression($period, $date='CURRENT_DATE')
|
||||
{
|
||||
if ($date!='CURRENT_DATE')
|
||||
{
|
||||
$date = '\''.$date.'\'';
|
||||
}
|
||||
|
||||
return 'SUBDATE('.$date.',INTERVAL '.$period.' DAY)';
|
||||
}
|
||||
|
||||
function pwg_db_get_recent_period($period, $date='CURRENT_DATE')
|
||||
{
|
||||
$query = '
|
||||
SELECT '.pwg_db_get_recent_period_expression($period);
|
||||
list($d) = pwg_db_fetch_row(pwg_query($query));
|
||||
|
||||
return $d;
|
||||
}
|
||||
|
||||
function pwg_db_get_flood_period_expression($seconds)
|
||||
{
|
||||
return 'SUBDATE(now(), INTERVAL '.$seconds.' SECOND)';
|
||||
}
|
||||
|
||||
function pwg_db_get_hour($date)
|
||||
{
|
||||
return 'hour('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_date_YYYYMM($date)
|
||||
{
|
||||
return 'DATE_FORMAT('.$date.', \'%Y%m\')';
|
||||
}
|
||||
|
||||
function pwg_db_get_date_MMDD($date)
|
||||
{
|
||||
return 'DATE_FORMAT('.$date.', \'%m%d\')';
|
||||
}
|
||||
|
||||
function pwg_db_get_year($date)
|
||||
{
|
||||
return 'YEAR('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_month($date)
|
||||
{
|
||||
return 'MONTH('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_week($date, $mode=null)
|
||||
{
|
||||
if ($mode)
|
||||
{
|
||||
return 'WEEK('.$date.', '.$mode.')';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'WEEK('.$date.')';
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_get_dayofmonth($date)
|
||||
{
|
||||
return 'DAYOFMONTH('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_dayofweek($date)
|
||||
{
|
||||
return 'DAYOFWEEK('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_weekday($date)
|
||||
{
|
||||
return 'WEEKDAY('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_date_to_ts($date)
|
||||
{
|
||||
return 'UNIX_TIMESTAMP('.$date.')';
|
||||
}
|
||||
|
||||
// my_error returns (or send to standard output) the message concerning the
|
||||
// error occured for the last mysql query.
|
||||
function my_error($header, $die)
|
||||
{
|
||||
$error = "[mysql error ".mysql_errno().'] '.mysql_error()."\n";
|
||||
$error .= $header;
|
||||
|
||||
if ($die)
|
||||
{
|
||||
fatal_error($error);
|
||||
}
|
||||
echo("<pre>");
|
||||
trigger_error($error, E_USER_WARNING);
|
||||
echo("</pre>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an data array from a SQL query.
|
||||
* Depending on $key_name and $value_name it can return :
|
||||
*
|
||||
* - an array of arrays of all fields (key=null, value=null)
|
||||
* array(
|
||||
* array('id'=>1, 'name'=>'DSC8956', ...),
|
||||
* array('id'=>2, 'name'=>'DSC8957', ...),
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* - an array of a single field (key=null, value='...')
|
||||
* array('DSC8956', 'DSC8957', ...)
|
||||
*
|
||||
* - an associative array of array of all fields (key='...', value=null)
|
||||
* array(
|
||||
* 'DSC8956' => array('id'=>1, 'name'=>'DSC8956', ...),
|
||||
* 'DSC8957' => array('id'=>2, 'name'=>'DSC8957', ...),
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* - an associative array of a single field (key='...', value='...')
|
||||
* array(
|
||||
* 'DSC8956' => 1,
|
||||
* 'DSC8957' => 2,
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param string $query
|
||||
* @param string $key_name
|
||||
* @param string $value_name
|
||||
* @return array
|
||||
*/
|
||||
function query2array($query, $key_name=null, $value_name=null)
|
||||
{
|
||||
$result = pwg_query($query);
|
||||
$data = array();
|
||||
|
||||
if (isset($key_name))
|
||||
{
|
||||
if (isset($value_name))
|
||||
{
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
$data[ $row[$key_name] ] = $row[$value_name];
|
||||
}
|
||||
else
|
||||
{
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
$data[ $row[$key_name] ] = $row;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($value_name))
|
||||
{
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
$data[] = $row[$value_name];
|
||||
}
|
||||
else
|
||||
{
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,921 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\mysql
|
||||
*/
|
||||
|
||||
define('DB_ENGINE', 'MySQL');
|
||||
define('REQUIRED_MYSQL_VERSION', '5.0.0');
|
||||
|
||||
define('DB_REGEX_OPERATOR', 'REGEXP');
|
||||
define('DB_RANDOM_FUNCTION', 'RAND');
|
||||
|
||||
|
||||
/**
|
||||
* Connect to database and store MySQLi resource in __$mysqli__ global variable.
|
||||
*
|
||||
* @param string $host
|
||||
* - localhost
|
||||
* - 1.2.3.4:3405
|
||||
* - /path/to/socket
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $database
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function pwg_db_connect($host, $user, $password, $database)
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
$port = null;
|
||||
$socket = null;
|
||||
|
||||
if (strpos($host, '/') === 0)
|
||||
{
|
||||
$socket = $host;
|
||||
$host = null;
|
||||
}
|
||||
elseif (strpos($host, ':') !== false)
|
||||
{
|
||||
list($host, $port) = explode(':', $host);
|
||||
}
|
||||
|
||||
$dbname = '';
|
||||
|
||||
$mysqli = new mysqli($host, $user, $password, $dbname, $port, $socket);
|
||||
if (mysqli_connect_error())
|
||||
{
|
||||
throw new Exception("Can't connect to server");
|
||||
}
|
||||
if (!$mysqli->select_db($database))
|
||||
{
|
||||
throw new Exception('Connection to server succeed, but it was impossible to connect to database');
|
||||
}
|
||||
|
||||
// MySQL 5.7 default settings forbid to select a colum that is not in the
|
||||
// group by. We've used that in Piwigo, for years. As an immediate solution
|
||||
// we can remove this constraint in the current MySQL session.
|
||||
list($sql_mode_current) = pwg_db_fetch_row(pwg_query('SELECT @@SESSION.sql_mode'));
|
||||
|
||||
// remove ONLY_FULL_GROUP_BY from the list
|
||||
$sql_mode_altered = implode(',', array_diff(explode(',', $sql_mode_current), array('ONLY_FULL_GROUP_BY')));
|
||||
|
||||
if ($sql_mode_altered != $sql_mode_current)
|
||||
{
|
||||
pwg_query("SET SESSION sql_mode='".$sql_mode_altered."'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charset for database connection.
|
||||
*/
|
||||
function pwg_db_check_charset()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
$db_charset = 'utf8';
|
||||
if (defined('DB_CHARSET') and DB_CHARSET != '')
|
||||
{
|
||||
$db_charset = DB_CHARSET;
|
||||
}
|
||||
$mysqli->set_charset($db_charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check MySQL version. Can call fatal_error().
|
||||
*/
|
||||
function pwg_db_check_version()
|
||||
{
|
||||
$current_mysql = pwg_get_db_version();
|
||||
if (version_compare($current_mysql, REQUIRED_MYSQL_VERSION, '<'))
|
||||
{
|
||||
fatal_error(
|
||||
sprintf(
|
||||
'your MySQL version is too old, you have "%s" and you need at least "%s"',
|
||||
$current_mysql,
|
||||
REQUIRED_MYSQL_VERSION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Mysql Version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function pwg_get_db_version()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->server_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query
|
||||
*
|
||||
* @param string $query
|
||||
* @return mysqli_result|bool
|
||||
*/
|
||||
function pwg_query($query)
|
||||
{
|
||||
global $mysqli, $conf, $page, $debug, $t2;
|
||||
|
||||
$start = microtime(true);
|
||||
($result = $mysqli->query($query)) or my_error($query, $conf['die_on_sql_error']);
|
||||
|
||||
$time = microtime(true) - $start;
|
||||
|
||||
if (!isset($page['count_queries']))
|
||||
{
|
||||
$page['count_queries'] = 0;
|
||||
$page['queries_time'] = 0;
|
||||
}
|
||||
|
||||
$page['count_queries']++;
|
||||
$page['queries_time']+= $time;
|
||||
|
||||
if ($conf['show_queries'])
|
||||
{
|
||||
$output = '';
|
||||
$output.= '<pre>['.$page['count_queries'].'] ';
|
||||
$output.= "\n".$query;
|
||||
$output.= "\n".'(this query time : ';
|
||||
$output.= '<b>'.number_format($time, 3, '.', ' ').' s)</b>';
|
||||
$output.= "\n".'(total SQL time : ';
|
||||
$output.= number_format($page['queries_time'], 3, '.', ' ').' s)';
|
||||
$output.= "\n".'(total time : ';
|
||||
$output.= number_format( ($time+$start-$t2), 3, '.', ' ').' s)';
|
||||
if ( $result!=null and preg_match('/\s*SELECT\s+/i',$query) )
|
||||
{
|
||||
$output.= "\n".'(num rows : ';
|
||||
$output.= pwg_db_num_rows($result).' )';
|
||||
}
|
||||
elseif ( $result!=null
|
||||
and preg_match('/\s*INSERT|UPDATE|REPLACE|DELETE\s+/i',$query) )
|
||||
{
|
||||
$output.= "\n".'(affected rows : ';
|
||||
$output.= pwg_db_changes().' )';
|
||||
}
|
||||
$output.= "</pre>\n";
|
||||
|
||||
$debug .= $output;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max value plus one of a particular column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $table
|
||||
* @param int
|
||||
*/
|
||||
function pwg_db_nextval($column, $table)
|
||||
{
|
||||
$query = '
|
||||
SELECT IF(MAX('.$column.')+1 IS NULL, 1, MAX('.$column.')+1)
|
||||
FROM '.$table;
|
||||
list($next) = pwg_db_fetch_row(pwg_query($query));
|
||||
|
||||
return $next;
|
||||
}
|
||||
|
||||
function pwg_db_changes()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->affected_rows;
|
||||
}
|
||||
|
||||
function pwg_db_num_rows($result)
|
||||
{
|
||||
return $result->num_rows ?? 0;
|
||||
}
|
||||
|
||||
function pwg_db_fetch_array($result)
|
||||
{
|
||||
return $result->fetch_array();
|
||||
}
|
||||
|
||||
function pwg_db_fetch_assoc($result)
|
||||
{
|
||||
return $result->fetch_assoc();
|
||||
}
|
||||
|
||||
function pwg_db_fetch_row($result)
|
||||
{
|
||||
return $result->fetch_row();
|
||||
}
|
||||
|
||||
function pwg_db_fetch_object($result)
|
||||
{
|
||||
return $result->fetch_object();
|
||||
}
|
||||
|
||||
function pwg_db_free_result($result)
|
||||
{
|
||||
return $result->free_result();
|
||||
}
|
||||
|
||||
function pwg_db_real_escape_string($s)
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return isset($s) ? $mysqli->real_escape_string($s) : null;
|
||||
}
|
||||
|
||||
function pwg_db_insert_id()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->insert_id;
|
||||
}
|
||||
|
||||
function pwg_db_errno()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->errno;
|
||||
}
|
||||
|
||||
function pwg_db_error()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->error;
|
||||
}
|
||||
|
||||
function pwg_db_close()
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
return $mysqli->close();
|
||||
}
|
||||
|
||||
|
||||
define('MASS_UPDATES_SKIP_EMPTY', 1);
|
||||
|
||||
/**
|
||||
* Updates multiple lines in a table.
|
||||
*
|
||||
* @param string $tablename
|
||||
* @param array $dbfields - contains 'primary' and 'update' arrays
|
||||
* @param array $datas - indexed by column names
|
||||
* @param int $flags - if MASS_UPDATES_SKIP_EMPTY, empty values do not overwrite existing ones
|
||||
*/
|
||||
function mass_updates($tablename, $dbfields, $datas, $flags=0)
|
||||
{
|
||||
if (count($datas) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we use the multi table update or N update queries
|
||||
if (count($datas) < 10)
|
||||
{
|
||||
foreach ($datas as $data)
|
||||
{
|
||||
$is_first = true;
|
||||
|
||||
$query = '
|
||||
UPDATE '.protect_column_name($tablename).'
|
||||
SET ';
|
||||
|
||||
foreach ($dbfields['update'] as $key)
|
||||
{
|
||||
$separator = $is_first ? '' : ",\n ";
|
||||
|
||||
if (isset($data[$key]) and $data[$key] != '')
|
||||
{
|
||||
$query.= $separator.protect_column_name($key).' = \''.$data[$key].'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($flags & MASS_UPDATES_SKIP_EMPTY)
|
||||
{
|
||||
continue; // next field
|
||||
}
|
||||
$query.= $separator.protect_column_name($key).' = NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
if (!$is_first)
|
||||
{// only if one field at least updated
|
||||
$is_first = true;
|
||||
|
||||
$query.= '
|
||||
WHERE ';
|
||||
foreach ($dbfields['primary'] as $key)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query.= ' AND ';
|
||||
}
|
||||
if (isset($data[$key]))
|
||||
{
|
||||
$query.= protect_column_name($key).' = \''.$data[$key].'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= protect_column_name($key).' IS NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
pwg_query($query);
|
||||
}
|
||||
} // foreach update
|
||||
} // if count<X
|
||||
else
|
||||
{
|
||||
// creation of the temporary table
|
||||
$result = pwg_query('SHOW FULL COLUMNS FROM '.protect_column_name($tablename));
|
||||
$columns = array();
|
||||
$all_fields = array_merge($dbfields['primary'], $dbfields['update']);
|
||||
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if (in_array($row['Field'], $all_fields))
|
||||
{
|
||||
$column = '`'.$row['Field'].'`';
|
||||
$column.= ' '.$row['Type'];
|
||||
|
||||
$nullable = true;
|
||||
if (!isset($row['Null']) or $row['Null'] == '' or $row['Null']=='NO')
|
||||
{
|
||||
$column.= ' NOT NULL';
|
||||
$nullable = false;
|
||||
}
|
||||
if (isset($row['Default']))
|
||||
{
|
||||
$column.= " default '".$row['Default']."'";
|
||||
}
|
||||
elseif ($nullable)
|
||||
{
|
||||
$column.= " default NULL";
|
||||
}
|
||||
if (isset($row['Collation']) and $row['Collation'] != 'NULL')
|
||||
{
|
||||
$column.= " collate '".$row['Collation']."'";
|
||||
}
|
||||
$columns[] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
$temporary_tablename = $tablename.'_'.micro_seconds();
|
||||
|
||||
$query = '
|
||||
CREATE TABLE '.$temporary_tablename.'
|
||||
(
|
||||
'.implode(",\n ", $columns).',
|
||||
UNIQUE KEY the_key ('.implode(',', $dbfields['primary']).')
|
||||
)';
|
||||
|
||||
pwg_query($query);
|
||||
mass_inserts($temporary_tablename, $all_fields, $datas);
|
||||
|
||||
if ($flags & MASS_UPDATES_SKIP_EMPTY)
|
||||
$func_set = function($s) { return "t1.$s = IFNULL(t2.$s, t1.$s)"; };
|
||||
else
|
||||
$func_set = function($s) { return "t1.$s = t2.$s"; };
|
||||
|
||||
// update of table by joining with temporary table
|
||||
$query = '
|
||||
UPDATE '.protect_column_name($tablename).' AS t1, '.$temporary_tablename.' AS t2
|
||||
SET '.
|
||||
implode(
|
||||
"\n , ",
|
||||
array_map($func_set,$dbfields['update'])
|
||||
).'
|
||||
WHERE '.
|
||||
implode(
|
||||
"\n AND ",
|
||||
array_map(
|
||||
function($s) { return "t1.$s = t2.$s"; },
|
||||
$dbfields['primary']
|
||||
)
|
||||
);
|
||||
pwg_query($query);
|
||||
|
||||
pwg_query('DROP TABLE '.$temporary_tablename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one line in a table.
|
||||
*
|
||||
* @param string $tablename
|
||||
* @param array $datas
|
||||
* @param array $where
|
||||
* @param int $flags - if MASS_UPDATES_SKIP_EMPTY, empty values do not overwrite existing ones
|
||||
*/
|
||||
function single_update($tablename, $datas, $where, $flags=0)
|
||||
{
|
||||
if (count($datas) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$is_first = true;
|
||||
|
||||
$query = '
|
||||
UPDATE '.protect_column_name($tablename).'
|
||||
SET ';
|
||||
|
||||
foreach ($datas as $key => $value)
|
||||
{
|
||||
$separator = $is_first ? '' : ",\n ";
|
||||
|
||||
if (isset($value) and $value !== '')
|
||||
{
|
||||
$query.= $separator.protect_column_name($key).' = \''.$value.'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($flags & MASS_UPDATES_SKIP_EMPTY)
|
||||
{
|
||||
continue; // next field
|
||||
}
|
||||
$query.= $separator.protect_column_name($key).' = NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
if (!$is_first)
|
||||
{// only if one field at least updated
|
||||
$is_first = true;
|
||||
|
||||
$query.= '
|
||||
WHERE ';
|
||||
|
||||
foreach ($where as $key => $value)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query.= ' AND ';
|
||||
}
|
||||
if (isset($value))
|
||||
{
|
||||
$query.= protect_column_name($key).' = \''.$value.'\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= protect_column_name($key).' IS NULL';
|
||||
}
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts multiple lines in a table.
|
||||
*
|
||||
* @param string $table_name
|
||||
* @param array $dbfields - fields from $datas which will be used
|
||||
* @param array $datas
|
||||
* @param array $options
|
||||
* - boolean ignore - use "INSERT IGNORE"
|
||||
*/
|
||||
function mass_inserts($table_name, $dbfields, $datas, $options=array())
|
||||
{
|
||||
$ignore = '';
|
||||
if (isset($options['ignore']) and $options['ignore'])
|
||||
{
|
||||
$ignore = 'IGNORE';
|
||||
}
|
||||
|
||||
if (count($datas) != 0)
|
||||
{
|
||||
$first = true;
|
||||
|
||||
$query = 'SHOW VARIABLES LIKE \'max_allowed_packet\'';
|
||||
list(, $packet_size) = pwg_db_fetch_row(pwg_query($query));
|
||||
$packet_size = $packet_size - 2000; // The last list of values MUST not exceed 2000 character*/
|
||||
$query = '';
|
||||
|
||||
foreach ($datas as $insert)
|
||||
{
|
||||
if (strlen($query) >= $packet_size)
|
||||
{
|
||||
pwg_query($query);
|
||||
$first = true;
|
||||
}
|
||||
|
||||
if ($first)
|
||||
{
|
||||
$query = '
|
||||
INSERT '.$ignore.' INTO '.protect_column_name($table_name).'
|
||||
('.implode(',', array_map('protect_column_name', $dbfields)).')
|
||||
VALUES';
|
||||
$first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= '
|
||||
, ';
|
||||
}
|
||||
|
||||
$query .= '(';
|
||||
foreach ($dbfields as $field_id => $dbfield)
|
||||
{
|
||||
if ($field_id > 0)
|
||||
{
|
||||
$query .= ',';
|
||||
}
|
||||
|
||||
if (!isset($insert[$dbfield]) or $insert[$dbfield] === '')
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= "'".$insert[$dbfield]."'";
|
||||
}
|
||||
}
|
||||
$query .= ')';
|
||||
}
|
||||
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts one line in a table.
|
||||
*
|
||||
* @param string $table_name
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* - boolean ignore - use "INSERT IGNORE"
|
||||
*/
|
||||
function single_insert($table_name, $data, $options=array())
|
||||
{
|
||||
$ignore = '';
|
||||
if (isset($options['ignore']) and $options['ignore'])
|
||||
{
|
||||
$ignore = 'IGNORE';
|
||||
}
|
||||
|
||||
if (count($data) != 0)
|
||||
{
|
||||
$query = '
|
||||
INSERT '.$ignore.' INTO '.protect_column_name($table_name).'
|
||||
('.implode(',', array_map('protect_column_name', array_keys($data))).')
|
||||
VALUES';
|
||||
|
||||
$query .= '(';
|
||||
$is_first = true;
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
if (!$is_first)
|
||||
{
|
||||
$query .= ',';
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_first = false;
|
||||
}
|
||||
|
||||
if ($value === '' || is_null($value))
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= "'".$value."'";
|
||||
}
|
||||
}
|
||||
$query .= ')';
|
||||
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
function protect_column_name($column_name)
|
||||
{
|
||||
if ('`' != $column_name[0])
|
||||
{
|
||||
$column_name = '`'.$column_name.'`';
|
||||
}
|
||||
|
||||
return $column_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do maintenance on all Piwigo tables
|
||||
*/
|
||||
function do_maintenance_all_tables()
|
||||
{
|
||||
global $prefixeTable, $page;
|
||||
|
||||
$all_tables = array();
|
||||
|
||||
// List all tables
|
||||
$query = 'SHOW TABLES LIKE \''.$prefixeTable.'%\'';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_row($result))
|
||||
{
|
||||
$all_tables[] = $row[0];
|
||||
}
|
||||
|
||||
// Repair all tables
|
||||
$query = 'REPAIR TABLE '.implode(', ', $all_tables);
|
||||
$mysqli_rc = pwg_query($query);
|
||||
|
||||
// Re-Order all tables
|
||||
foreach ($all_tables as $table_name)
|
||||
{
|
||||
$all_primary_key = array();
|
||||
|
||||
$query = 'DESC '.$table_name.';';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if ($row['Key'] == 'PRI')
|
||||
{
|
||||
$all_primary_key[] = $row['Field'];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($all_primary_key) != 0)
|
||||
{
|
||||
$query = 'ALTER TABLE '.$table_name.' ORDER BY '.implode(', ', $all_primary_key).';';
|
||||
$mysqli_rc = $mysqli_rc && pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize all tables
|
||||
$query = 'OPTIMIZE TABLE '.implode(', ', $all_tables);
|
||||
$mysqli_rc = $mysqli_rc && pwg_query($query);
|
||||
if ($mysqli_rc)
|
||||
{
|
||||
$page['infos'][] = l10n('All optimizations have been successfully completed.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['errors'][] = l10n('Optimizations have been completed with some errors.');
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_concat($array)
|
||||
{
|
||||
$string = implode(',', $array);
|
||||
return 'CONCAT('. $string.')';
|
||||
}
|
||||
|
||||
function pwg_db_concat_ws($array, $separator)
|
||||
{
|
||||
$string = implode(',', $array);
|
||||
return 'CONCAT_WS(\''.$separator.'\','. $string.')';
|
||||
}
|
||||
|
||||
function pwg_db_cast_to_text($string)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the possible values of an enum field.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @return string[]
|
||||
*/
|
||||
function get_enums($table, $field)
|
||||
{
|
||||
$result = pwg_query('DESC '.$table);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if ($row['Field'] == $field)
|
||||
{
|
||||
// parse enum('blue','green','black')
|
||||
$options = explode(',', substr($row['Type'], 5, -1));
|
||||
foreach ($options as $i => $option)
|
||||
{
|
||||
$options[$i] = str_replace("'", '',$option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pwg_db_free_result($result);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a variable is equivalent to true or false.
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return bool
|
||||
*/
|
||||
function get_boolean($input)
|
||||
{
|
||||
if ('false' === strtolower($input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)$input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string 'true' or 'false' if the given var is boolean.
|
||||
* If the input is another type, it is not changed.
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return mixed
|
||||
*/
|
||||
function boolean_to_string($var)
|
||||
{
|
||||
if (is_bool($var))
|
||||
{
|
||||
return $var ? 'true' : 'false';
|
||||
}
|
||||
else
|
||||
{
|
||||
return $var;
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_get_recent_period_expression($period, $date='CURRENT_DATE')
|
||||
{
|
||||
if ($date!='CURRENT_DATE')
|
||||
{
|
||||
$date = '\''.$date.'\'';
|
||||
}
|
||||
|
||||
return 'SUBDATE('.$date.',INTERVAL '.$period.' DAY)';
|
||||
}
|
||||
|
||||
function pwg_db_get_recent_period($period, $date='CURRENT_DATE')
|
||||
{
|
||||
$query = '
|
||||
SELECT '.pwg_db_get_recent_period_expression($period);
|
||||
list($d) = pwg_db_fetch_row(pwg_query($query));
|
||||
|
||||
return $d;
|
||||
}
|
||||
|
||||
function pwg_db_get_flood_period_expression($seconds)
|
||||
{
|
||||
return 'SUBDATE(NOW(), INTERVAL '.$seconds.' SECOND)';
|
||||
}
|
||||
|
||||
function pwg_db_get_hour($date)
|
||||
{
|
||||
return 'HOUR('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_date_YYYYMM($date)
|
||||
{
|
||||
return 'DATE_FORMAT('.$date.', \'%Y%m\')';
|
||||
}
|
||||
|
||||
function pwg_db_get_date_MMDD($date)
|
||||
{
|
||||
return 'DATE_FORMAT('.$date.', \'%m%d\')';
|
||||
}
|
||||
|
||||
function pwg_db_get_year($date)
|
||||
{
|
||||
return 'YEAR('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_month($date)
|
||||
{
|
||||
return 'MONTH('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_week($date, $mode=null)
|
||||
{
|
||||
if ($mode)
|
||||
{
|
||||
return 'WEEK('.$date.', '.$mode.')';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'WEEK('.$date.')';
|
||||
}
|
||||
}
|
||||
|
||||
function pwg_db_get_dayofmonth($date)
|
||||
{
|
||||
return 'DAYOFMONTH('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_dayofweek($date)
|
||||
{
|
||||
return 'DAYOFWEEK('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_get_weekday($date)
|
||||
{
|
||||
return 'WEEKDAY('.$date.')';
|
||||
}
|
||||
|
||||
function pwg_db_date_to_ts($date)
|
||||
{
|
||||
return 'UNIX_TIMESTAMP('.$date.')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns (or send to standard output) the message concerning the
|
||||
* error occured for the last mysql query.
|
||||
*/
|
||||
function my_error($header, $die)
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
$error = "[mysql error ".$mysqli->errno.'] '.$mysqli->error."\n";
|
||||
$error .= $header;
|
||||
|
||||
if ($die)
|
||||
{
|
||||
fatal_error($error);
|
||||
}
|
||||
echo("<pre>");
|
||||
trigger_error($error, E_USER_WARNING);
|
||||
echo("</pre>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an data array from a SQL query.
|
||||
* Depending on $key_name and $value_name it can return :
|
||||
*
|
||||
* - an array of arrays of all fields (key=null, value=null)
|
||||
* array(
|
||||
* array('id'=>1, 'name'=>'DSC8956', ...),
|
||||
* array('id'=>2, 'name'=>'DSC8957', ...),
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* - an array of a single field (key=null, value='...')
|
||||
* array('DSC8956', 'DSC8957', ...)
|
||||
*
|
||||
* - an associative array of array of all fields (key='...', value=null)
|
||||
* array(
|
||||
* 'DSC8956' => array('id'=>1, 'name'=>'DSC8956', ...),
|
||||
* 'DSC8957' => array('id'=>2, 'name'=>'DSC8957', ...),
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* - an associative array of a single field (key='...', value='...')
|
||||
* array(
|
||||
* 'DSC8956' => 1,
|
||||
* 'DSC8957' => 2,
|
||||
* ...
|
||||
* )
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param string $query
|
||||
* @param string $key_name
|
||||
* @param string $value_name
|
||||
* @return array
|
||||
*/
|
||||
function query2array($query, $key_name=null, $value_name=null)
|
||||
{
|
||||
$result = pwg_query($query);
|
||||
$data = array();
|
||||
|
||||
if (isset($key_name))
|
||||
{
|
||||
if (isset($value_name))
|
||||
{
|
||||
while ($row = $result->fetch_assoc())
|
||||
$data[ $row[$key_name] ] = $row[$value_name];
|
||||
}
|
||||
else
|
||||
{
|
||||
while ($row = $result->fetch_assoc())
|
||||
$data[ $row[$key_name] ] = $row;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($value_name))
|
||||
{
|
||||
while ($row = $result->fetch_assoc())
|
||||
$data[] = $row[$value_name];
|
||||
}
|
||||
else
|
||||
{
|
||||
while ($row = $result->fetch_assoc())
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,535 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package Derivatives
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A source image is used to get a derivative image. It is either
|
||||
* the original file for a jpg/png/... or a 'representative' image
|
||||
* of a non image file or a standard icon for the non-image file.
|
||||
*/
|
||||
final class SrcImage
|
||||
{
|
||||
const IS_ORIGINAL = 0x01;
|
||||
const IS_MIMETYPE = 0x02;
|
||||
const DIM_NOT_GIVEN = 0x04;
|
||||
|
||||
/** @var int */
|
||||
public $id;
|
||||
/** @var string */
|
||||
public $rel_path;
|
||||
/** @var int */
|
||||
public $rotation = 0;
|
||||
/** @var int[] */
|
||||
private $size=null;
|
||||
/** @var int */
|
||||
private $flags=0;
|
||||
|
||||
/**
|
||||
* @param array $infos assoc array of data from images table
|
||||
*/
|
||||
function __construct($infos)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$this->id = $infos['id'];
|
||||
$ext = strtolower(get_extension($infos['path']));
|
||||
$infos['file_ext'] = @strtolower(get_extension($infos['file']));
|
||||
$infos['path_ext'] = $ext;
|
||||
if (in_array($ext, $conf['picture_ext']))
|
||||
{
|
||||
$this->rel_path = $infos['path'];
|
||||
$this->flags |= self::IS_ORIGINAL;
|
||||
}
|
||||
elseif (!empty($infos['representative_ext']))
|
||||
{
|
||||
$this->rel_path = original_to_representative($infos['path'], $infos['representative_ext']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->rel_path = trigger_change('get_mimetype_location', get_themeconf('mime_icon_dir').$ext.'.png', $ext );
|
||||
$this->flags |= self::IS_MIMETYPE;
|
||||
if ( ($size=@getimagesize(PHPWG_ROOT_PATH.$this->rel_path)) === false)
|
||||
{
|
||||
if ('svg' == $ext)
|
||||
{
|
||||
$this->rel_path = $infos['path'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->rel_path = 'themes/default/icon/mimetypes/unknown.png';
|
||||
}
|
||||
$size = getimagesize(PHPWG_ROOT_PATH.$this->rel_path);
|
||||
}
|
||||
$this->size = @array($size[0],$size[1]);
|
||||
}
|
||||
|
||||
if (!$this->size)
|
||||
{
|
||||
if (isset($infos['width']) && isset($infos['height']))
|
||||
{
|
||||
$width = $infos['width'];
|
||||
$height = $infos['height'];
|
||||
|
||||
$this->rotation = intval($infos['rotation']) % 4;
|
||||
// 1 or 5 => 90 clockwise
|
||||
// 3 or 7 => 270 clockwise
|
||||
if ($this->rotation % 2)
|
||||
{
|
||||
$width = $infos['height'];
|
||||
$height = $infos['width'];
|
||||
}
|
||||
|
||||
$this->size = array($width, $height);
|
||||
}
|
||||
elseif (!array_key_exists('width', $infos))
|
||||
{
|
||||
$this->flags |= self::DIM_NOT_GIVEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function is_original()
|
||||
{
|
||||
return $this->flags & self::IS_ORIGINAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function is_mimetype()
|
||||
{
|
||||
return $this->flags & self::IS_MIMETYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function get_path()
|
||||
{
|
||||
return PHPWG_ROOT_PATH.$this->rel_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function get_url()
|
||||
{
|
||||
$url = get_root_url().$this->rel_path;
|
||||
if ( !($this->flags & self::IS_MIMETYPE) )
|
||||
{
|
||||
$url = trigger_change('get_src_image_url', $url, $this);
|
||||
}
|
||||
return embellish_url($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function has_size()
|
||||
{
|
||||
return $this->size != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]|null 0=width, 1=height or null if fail to compute size
|
||||
*/
|
||||
function get_size()
|
||||
{
|
||||
if ($this->size == null)
|
||||
{
|
||||
if ($this->flags & self::DIM_NOT_GIVEN)
|
||||
fatal_error('SrcImage dimensions required but not provided');
|
||||
// probably not metadata synced
|
||||
if ( ($size = getimagesize( $this->get_path() )) !== false)
|
||||
{
|
||||
$this->size = array($size[0],$size[1]);
|
||||
pwg_query('UPDATE '.IMAGES_TABLE.' SET width='.$size[0].', height='.$size[1].' WHERE id='.$this->id);
|
||||
}
|
||||
}
|
||||
return $this->size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Holds information (path, url, dimensions) about a derivative image.
|
||||
* A derivative image is constructed from a source image (SrcImage class)
|
||||
* and derivative parameters (DerivativeParams class).
|
||||
*/
|
||||
final class DerivativeImage
|
||||
{
|
||||
/** @var SrcImage */
|
||||
public $src_image;
|
||||
/** @var array */
|
||||
private $params;
|
||||
/** @var string */
|
||||
private $rel_path;
|
||||
/** @var string */
|
||||
private $rel_url;
|
||||
/** @var bool */
|
||||
private $is_cached=true;
|
||||
|
||||
/**
|
||||
* @param string|DerivativeParams $type standard derivative param type (e.g. IMG_*)
|
||||
* or a DerivativeParams object
|
||||
* @param SrcImage $src_image the source image of this derivative
|
||||
*/
|
||||
function __construct($type, SrcImage $src_image)
|
||||
{
|
||||
$this->src_image = $src_image;
|
||||
if (is_string($type))
|
||||
{
|
||||
$this->params = ImageStdParams::get_by_type($type);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->params = $type;
|
||||
}
|
||||
|
||||
self::build($src_image, $this->params, $this->rel_path, $this->rel_url, $this->is_cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the url of a thumbnail.
|
||||
*
|
||||
* @param array|SrcImage $infos array of info from db or SrcImage
|
||||
* @return string
|
||||
*/
|
||||
static function thumb_url($infos)
|
||||
{
|
||||
return self::url(IMG_THUMB, $infos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the url for a particular photo size.
|
||||
*
|
||||
* @param string|DerivativeParams $type standard derivative param type (e.g. IMG_*)
|
||||
* or a DerivativeParams object
|
||||
* @param array|SrcImage $infos array of info from db or SrcImage
|
||||
* @return string
|
||||
*/
|
||||
static function url($type, $infos)
|
||||
{
|
||||
$src_image = is_object($infos) ? $infos : new SrcImage($infos);
|
||||
$params = is_string($type) ? ImageStdParams::get_by_type($type) : $type;
|
||||
self::build($src_image, $params, $rel_path, $rel_url);
|
||||
if ($params == null)
|
||||
{
|
||||
return $src_image->get_url();
|
||||
}
|
||||
return embellish_url(
|
||||
trigger_change('get_derivative_url',
|
||||
get_root_url().$rel_url,
|
||||
$params, $src_image, $rel_url
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return associative an array of all DerivativeImage for a specific image.
|
||||
* Disabled derivative types can be still found in the return, mapped to an
|
||||
* enabled derivative (e.g. the values are not unique in the return array).
|
||||
* This is useful for any plugin/theme to just use $deriv[IMG_XLARGE] even if
|
||||
* the XLARGE is disabled.
|
||||
*
|
||||
* @param array|SrcImage $src_image array of info from db or SrcImage
|
||||
* @return DerivativeImage[]
|
||||
*/
|
||||
static function get_all($src_image)
|
||||
{
|
||||
if (!is_object($src_image))
|
||||
{
|
||||
$src_image = new SrcImage($src_image);
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
// build enabled types
|
||||
foreach (ImageStdParams::get_defined_type_map() as $type => $params)
|
||||
{
|
||||
$derivative = new DerivativeImage($params, $src_image);
|
||||
$ret[$type] = $derivative;
|
||||
}
|
||||
// disabled types, fallback to enabled types
|
||||
foreach (ImageStdParams::get_undefined_type_map() as $type => $type2)
|
||||
{
|
||||
$ret[$type] = $ret[$type2];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of DerivativeImage for a specific image and size.
|
||||
* Disabled derivatives fallback to an enabled derivative.
|
||||
*
|
||||
* @param string $type standard derivative param type (e.g. IMG_*)
|
||||
* @param array|SrcImage $src_image array of info from db or SrcImage
|
||||
* @return DerivativeImage|null null if $type not found
|
||||
*/
|
||||
static function get_one($type, $src_image)
|
||||
{
|
||||
if (!is_object($src_image))
|
||||
{
|
||||
$src_image = new SrcImage($src_image);
|
||||
}
|
||||
|
||||
$defined = ImageStdParams::get_defined_type_map();
|
||||
if (isset($defined[$type]))
|
||||
{
|
||||
return new DerivativeImage($defined[$type], $src_image);
|
||||
}
|
||||
|
||||
$undefined = ImageStdParams::get_undefined_type_map();
|
||||
if (isset($undefined[$type]))
|
||||
{
|
||||
return new DerivativeImage($defined[ $undefined[$type] ], $src_image);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo : documentation of DerivativeImage::build
|
||||
*/
|
||||
private static function build($src, &$params, &$rel_path, &$rel_url, &$is_cached=null)
|
||||
{
|
||||
if ( $src->has_size() && $params->is_identity( $src->get_size() ) )
|
||||
{// the source image is smaller than what we should do - we do not upsample
|
||||
if (!$params->will_watermark($src->get_size()) && !$src->rotation)
|
||||
{// no watermark, no rotation required -> we will use the source image
|
||||
$params = null;
|
||||
$rel_path = $rel_url = $src->rel_path;
|
||||
return;
|
||||
}
|
||||
$defined_types = array_keys(ImageStdParams::get_defined_type_map());
|
||||
for ($i=0; $i<count($defined_types); $i++)
|
||||
{
|
||||
if ($defined_types[$i] == $params->type)
|
||||
{
|
||||
for ($i--; $i>=0; $i--)
|
||||
{
|
||||
$smaller = ImageStdParams::get_by_type($defined_types[$i]);
|
||||
if ($smaller->sizing->max_crop==$params->sizing->max_crop && $smaller->is_identity( $src->get_size() ))
|
||||
{
|
||||
$params = $smaller;
|
||||
self::build($src, $params, $rel_path, $rel_url, $is_cached);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tokens=array();
|
||||
$tokens[] = substr($params->type,0,2);
|
||||
|
||||
if ($params->type==IMG_CUSTOM)
|
||||
{
|
||||
$params->add_url_tokens($tokens);
|
||||
}
|
||||
|
||||
$loc = $src->rel_path;
|
||||
if (substr_compare($loc, './', 0, 2)==0)
|
||||
{
|
||||
$loc = substr($loc, 2);
|
||||
}
|
||||
elseif (substr_compare($loc, '../', 0, 3)==0)
|
||||
{
|
||||
$loc = substr($loc, 3);
|
||||
}
|
||||
$loc = substr_replace($loc, '-'.implode('_', $tokens), strrpos($loc, '.'), 0 );
|
||||
|
||||
$rel_path = PWG_DERIVATIVE_DIR.$loc;
|
||||
|
||||
global $conf;
|
||||
$url_style=$conf['derivative_url_style'];
|
||||
if (!$url_style)
|
||||
{
|
||||
$mtime = @filemtime(PHPWG_ROOT_PATH.$rel_path);
|
||||
if ($mtime===false or $mtime < $params->last_mod_time)
|
||||
{
|
||||
$is_cached = false;
|
||||
$url_style = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
$url_style = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($url_style == 2)
|
||||
{
|
||||
$rel_url = 'i';
|
||||
if ($conf['php_extension_in_urls']) $rel_url .= '.php';
|
||||
if ($conf['question_mark_in_urls']) $rel_url .= '?';
|
||||
$rel_url .= '/'.$loc;
|
||||
}
|
||||
else
|
||||
{
|
||||
$rel_url = $rel_path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function get_path()
|
||||
{
|
||||
return PHPWG_ROOT_PATH.$this->rel_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function get_url()
|
||||
{
|
||||
if ($this->params == null)
|
||||
{
|
||||
return $this->src_image->get_url();
|
||||
}
|
||||
return embellish_url(
|
||||
trigger_change('get_derivative_url',
|
||||
get_root_url().$this->rel_url,
|
||||
$this->params, $this->src_image, $this->rel_url
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function same_as_source()
|
||||
{
|
||||
return $this->params == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string one if IMG_* or 'Original'
|
||||
*/
|
||||
function get_type()
|
||||
{
|
||||
if ($this->params == null)
|
||||
return 'Original';
|
||||
return $this->params->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
function get_size()
|
||||
{
|
||||
if ($this->params == null)
|
||||
{
|
||||
return $this->src_image->get_size();
|
||||
}
|
||||
return $this->params->compute_final_size($this->src_image->get_size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size as CSS rule.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_size_css()
|
||||
{
|
||||
$size = $this->get_size();
|
||||
if ($size)
|
||||
{
|
||||
return 'width:'.$size[0].'px; height:'.$size[1].'px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size as HTML attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_size_htm()
|
||||
{
|
||||
$size = $this->get_size();
|
||||
if ($size)
|
||||
{
|
||||
return 'width="'.$size[0].'" height="'.$size[1].'"';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns literal size: $widthx$height.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_size_hr()
|
||||
{
|
||||
$size = $this->get_size();
|
||||
if ($size)
|
||||
{
|
||||
return $size[0].' x '.$size[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $maxw
|
||||
* @param int $mawh
|
||||
* @return int[]
|
||||
*/
|
||||
function get_scaled_size($maxw, $maxh)
|
||||
{
|
||||
$size = $this->get_size();
|
||||
if ($size)
|
||||
{
|
||||
$ratio_w = $size[0] / $maxw;
|
||||
$ratio_h = $size[1] / $maxh;
|
||||
if ($ratio_w>1 || $ratio_h>1)
|
||||
{
|
||||
if ($ratio_w > $ratio_h)
|
||||
{
|
||||
$size[0] = $maxw;
|
||||
$size[1] = floor($size[1] / $ratio_w);
|
||||
}
|
||||
else
|
||||
{
|
||||
$size[0] = floor($size[0] / $ratio_h);
|
||||
$size[1] = $maxh;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scaled size as HTML attributes.
|
||||
*
|
||||
* @param int $maxw
|
||||
* @param int $mawh
|
||||
* @return string
|
||||
*/
|
||||
function get_scaled_size_htm($maxw=9999, $maxh=9999)
|
||||
{
|
||||
$size = $this->get_scaled_size($maxw, $maxh);
|
||||
if ($size)
|
||||
{
|
||||
return 'width="'.$size[0].'" height="'.$size[1].'"';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function is_cached()
|
||||
{
|
||||
return $this->is_cached;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package Derivatives
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Formats a size name into a 2 chars identifier usable in filename.
|
||||
*
|
||||
* @param string $t one of IMG_*
|
||||
* @return string
|
||||
*/
|
||||
function derivative_to_url($t)
|
||||
{
|
||||
return substr($t, 0, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a size array into a identifier usable in filename.
|
||||
*
|
||||
* @param int[] $s
|
||||
* @return string
|
||||
*/
|
||||
function size_to_url($s)
|
||||
{
|
||||
if ($s[0]==$s[1])
|
||||
{
|
||||
return $s[0];
|
||||
}
|
||||
return $s[0].'x'.$s[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $s1
|
||||
* @param int[] $s2
|
||||
* @return bool
|
||||
*/
|
||||
function size_equals($s1, $s2)
|
||||
{
|
||||
return ($s1[0]==$s2[0] && $s1[1]==$s2[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a char a-z into a float.
|
||||
*
|
||||
* @param string
|
||||
* @return float
|
||||
*/
|
||||
function char_to_fraction($c)
|
||||
{
|
||||
return (ord($c) - ord('a'))/25;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a float into a char a-z.
|
||||
*
|
||||
* @param float
|
||||
* @return string
|
||||
*/
|
||||
function fraction_to_char($f)
|
||||
{
|
||||
return chr(ord('a') + round($f*25));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Small utility to manipulate a 'rectangle'.
|
||||
*/
|
||||
final class ImageRect
|
||||
{
|
||||
/**
|
||||
* @var int $l
|
||||
* @var int $t
|
||||
* @var int $r
|
||||
* @var int $b
|
||||
*/
|
||||
public $l,$t,$r,$b;
|
||||
|
||||
/**
|
||||
* @param int[] $l width and height
|
||||
*/
|
||||
function __construct($l)
|
||||
{
|
||||
$this->l = $this->t = 0;
|
||||
$this->r = $l[0];
|
||||
$this->b = $l[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function width()
|
||||
{
|
||||
return $this->r - $this->l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function height()
|
||||
{
|
||||
return $this->b - $this->t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops horizontally this rectangle by increasing left side and/or reducing the right side.
|
||||
*
|
||||
* @param int $pixels - the amount to substract from the width
|
||||
* @param stirng $coi - a 4 character string (or null) containing the center of interest
|
||||
*/
|
||||
function crop_h($pixels, $coi)
|
||||
{
|
||||
if ($this->width() <= $pixels)
|
||||
return;
|
||||
$tlcrop = floor($pixels/2);
|
||||
|
||||
if (!empty($coi))
|
||||
{
|
||||
$coil = floor($this->r * char_to_fraction($coi[0]));
|
||||
$coir = ceil($this->r * char_to_fraction($coi[2]));
|
||||
$availableL = $coil > $this->l ? $coil - $this->l : 0;
|
||||
$availableR = $coir < $this->r ? $this->r - $coir : 0;
|
||||
if ($availableL + $availableR >= $pixels)
|
||||
{
|
||||
if ($availableL < $tlcrop)
|
||||
{
|
||||
$tlcrop = $availableL;
|
||||
}
|
||||
elseif ($availableR < $tlcrop)
|
||||
{
|
||||
$tlcrop = $pixels - $availableR;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->l += $tlcrop;
|
||||
$this->r -= $pixels - $tlcrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops vertically this rectangle by increasing top side and/or reducing the bottom side.
|
||||
*
|
||||
* @param int $pixels - the amount to substract from the height
|
||||
* @param string $coi - a 4 character string (or null) containing the center of interest
|
||||
*/
|
||||
function crop_v($pixels, $coi)
|
||||
{
|
||||
if ($this->height() <= $pixels)
|
||||
return;
|
||||
$tlcrop = floor($pixels/2);
|
||||
|
||||
if (!empty($coi))
|
||||
{
|
||||
$coit = floor($this->b * char_to_fraction($coi[1]));
|
||||
$coib = ceil($this->b * char_to_fraction($coi[3]));
|
||||
$availableT = $coit > $this->t ? $coit - $this->t : 0;
|
||||
$availableB = $coib < $this->b ? $this->b - $coib : 0;
|
||||
if ($availableT + $availableB >= $pixels)
|
||||
{
|
||||
if ($availableT < $tlcrop)
|
||||
{
|
||||
$tlcrop = $availableT;
|
||||
}
|
||||
elseif ($availableB < $tlcrop)
|
||||
{
|
||||
$tlcrop = $pixels - $availableB;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->t += $tlcrop;
|
||||
$this->b -= $pixels - $tlcrop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paramaters for derivative scaling and cropping.
|
||||
* Instance of this class contained by DerivativeParams class.
|
||||
*/
|
||||
final class SizingParams
|
||||
{
|
||||
/** @var int[] */
|
||||
var $ideal_size;
|
||||
/** @var float */
|
||||
var $max_crop;
|
||||
/** @var int[] */
|
||||
var $min_size;
|
||||
|
||||
/**
|
||||
* @param int[] $ideal_size - two element array of maximum output dimensions (width, height)
|
||||
* @param float $max_crop - from 0=no cropping to 1= max cropping (100% of width/height);
|
||||
* expressed as a factor of the input width/height
|
||||
* @param int[] $min_size - (used only if _$max_crop_ !=0) two element array of output dimensions (width, height)
|
||||
*/
|
||||
function __construct($ideal_size, $max_crop=0, $min_size=null)
|
||||
{
|
||||
$this->ideal_size = $ideal_size;
|
||||
$this->max_crop = $max_crop;
|
||||
$this->min_size = $min_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a simple SizingParams object.
|
||||
*
|
||||
* @param int $w
|
||||
* @param int $h
|
||||
* @return SizingParams
|
||||
*/
|
||||
static function classic($w, $h)
|
||||
{
|
||||
return new SizingParams( array($w,$h) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a square SizingParams object.
|
||||
*
|
||||
* @param int $x
|
||||
* @return SizingParams
|
||||
*/
|
||||
static function square($w)
|
||||
{
|
||||
return new SizingParams( array($w,$w), 1, array($w,$w) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens depending on sizing configuration.
|
||||
*
|
||||
* @param array &$tokens
|
||||
*/
|
||||
function add_url_tokens(&$tokens)
|
||||
{
|
||||
if ($this->max_crop == 0)
|
||||
{
|
||||
$tokens[] = 's'.size_to_url($this->ideal_size);
|
||||
}
|
||||
elseif ($this->max_crop == 1 && size_equals($this->ideal_size, $this->min_size) )
|
||||
{
|
||||
$tokens[] = 'e'.size_to_url($this->ideal_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens[] = size_to_url($this->ideal_size);
|
||||
$tokens[] = fraction_to_char($this->max_crop);
|
||||
$tokens[] = size_to_url($this->min_size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the cropping rectangle and the scaled size for an input image size.
|
||||
*
|
||||
* @param int[] $in_size - two element array of input dimensions (width, height)
|
||||
* @param string $coi - four character encoded string containing the center of interest (unused if max_crop=0)
|
||||
* @param ImageRect &$crop_rect - ImageRect containing the cropping rectangle or null if cropping is not required
|
||||
* @param int[] &$scale_size - two element array containing width and height of the scaled image
|
||||
*/
|
||||
function compute($in_size, $coi, &$crop_rect, &$scale_size)
|
||||
{
|
||||
$destCrop = new ImageRect($in_size);
|
||||
|
||||
if ($this->max_crop > 0)
|
||||
{
|
||||
$ratio_w = $destCrop->width() / $this->ideal_size[0];
|
||||
$ratio_h = $destCrop->height() / $this->ideal_size[1];
|
||||
if ($ratio_w>1 || $ratio_h>1)
|
||||
{
|
||||
if ($ratio_w > $ratio_h)
|
||||
{
|
||||
$h = $destCrop->height() / $ratio_w;
|
||||
if ($h < $this->min_size[1])
|
||||
{
|
||||
$idealCropPx = $destCrop->width() - floor($destCrop->height() * $this->ideal_size[0] / $this->min_size[1]);
|
||||
$maxCropPx = round($this->max_crop * $destCrop->width());
|
||||
$destCrop->crop_h( min($idealCropPx, $maxCropPx), $coi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$w = $destCrop->width() / $ratio_h;
|
||||
if ($w < $this->min_size[0])
|
||||
{
|
||||
$idealCropPx = $destCrop->height() - floor($destCrop->width() * $this->ideal_size[1] / $this->min_size[0]);
|
||||
$maxCropPx = round($this->max_crop * $destCrop->height());
|
||||
$destCrop->crop_v( min($idealCropPx, $maxCropPx), $coi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scale_size = array($destCrop->width(), $destCrop->height());
|
||||
|
||||
$ratio_w = $destCrop->width() / $this->ideal_size[0];
|
||||
$ratio_h = $destCrop->height() / $this->ideal_size[1];
|
||||
if ($ratio_w>1 || $ratio_h>1)
|
||||
{
|
||||
if ($ratio_w > $ratio_h)
|
||||
{
|
||||
$scale_size[0] = $this->ideal_size[0];
|
||||
$scale_size[1] = floor(1e-6 + $scale_size[1] / $ratio_w);
|
||||
}
|
||||
else
|
||||
{
|
||||
$scale_size[0] = floor(1e-6 + $scale_size[0] / $ratio_h);
|
||||
$scale_size[1] = $this->ideal_size[1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$scale_size = null;
|
||||
}
|
||||
|
||||
$crop_rect = null;
|
||||
if ($destCrop->width()!=$in_size[0] || $destCrop->height()!=$in_size[1] )
|
||||
{
|
||||
$crop_rect = $destCrop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All needed parameters to generate a derivative image.
|
||||
*/
|
||||
final class DerivativeParams
|
||||
{
|
||||
/** @var SizingParams */
|
||||
public $sizing;
|
||||
/** @var string among IMG_* */
|
||||
public $type = IMG_CUSTOM;
|
||||
/** @var int used for non-custom images to regenerate the cached files */
|
||||
public $last_mod_time = 0;
|
||||
/** @var bool */
|
||||
public $use_watermark = false;
|
||||
/** @var float from 0=no sharpening to 1=max sharpening */
|
||||
public $sharpen = 0;
|
||||
|
||||
/**
|
||||
* @param SizingParams $sizing
|
||||
*/
|
||||
function __construct($sizing)
|
||||
{
|
||||
$this->sizing = $sizing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
return array('last_mod_time', 'sizing', 'sharpen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens depending on sizing configuration.
|
||||
*
|
||||
* @param array &$tokens
|
||||
*/
|
||||
function add_url_tokens(&$tokens)
|
||||
{
|
||||
$this->sizing->add_url_tokens($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
function compute_final_size($in_size)
|
||||
{
|
||||
$this->sizing->compute( $in_size, null, $crop_rect, $scale_size );
|
||||
return $scale_size != null ? $scale_size : $in_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function max_width()
|
||||
{
|
||||
return $this->sizing->ideal_size[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function max_height()
|
||||
{
|
||||
return $this->sizing->ideal_size[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo : description of DerivativeParams::is_identity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_identity($in_size)
|
||||
{
|
||||
if ($in_size[0] > $this->sizing->ideal_size[0] or
|
||||
$in_size[1] > $this->sizing->ideal_size[1] )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function will_watermark($out_size)
|
||||
{
|
||||
if ($this->use_watermark)
|
||||
{
|
||||
$min_size = ImageStdParams::get_watermark()->min_size;
|
||||
return $min_size[0]<=$out_size[0]
|
||||
|| $min_size[1]<=$out_size[1];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package Derivatives
|
||||
*/
|
||||
|
||||
|
||||
define('IMG_SQUARE', 'square');
|
||||
define('IMG_THUMB', 'thumb');
|
||||
define('IMG_XXSMALL', '2small');
|
||||
define('IMG_XSMALL', 'xsmall');
|
||||
define('IMG_SMALL', 'small');
|
||||
define('IMG_MEDIUM', 'medium');
|
||||
define('IMG_LARGE', 'large');
|
||||
define('IMG_XLARGE', 'xlarge');
|
||||
define('IMG_XXLARGE', 'xxlarge');
|
||||
define('IMG_3XLARGE', '3xlarge');
|
||||
define('IMG_4XLARGE', '4xlarge');
|
||||
define('IMG_CUSTOM', 'custom');
|
||||
|
||||
|
||||
/**
|
||||
* Container for watermark configuration.
|
||||
*/
|
||||
final class WatermarkParams
|
||||
{
|
||||
/** @var string */
|
||||
public $file = '';
|
||||
/** @var int[] */
|
||||
public $min_size = array(500,500);
|
||||
/** @var int */
|
||||
public $xpos = 50;
|
||||
/** @var int */
|
||||
public $ypos = 50;
|
||||
/** @var int */
|
||||
public $xrepeat = 0;
|
||||
/** @var int */
|
||||
public $yrepeat = 0;
|
||||
/** @var int */
|
||||
public $opacity = 100;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Container for standard derivatives parameters.
|
||||
*/
|
||||
final class ImageStdParams
|
||||
{
|
||||
/** @var string[] */
|
||||
private static $all_types = array(
|
||||
IMG_SQUARE, IMG_THUMB, IMG_XXSMALL, IMG_XSMALL, IMG_SMALL,
|
||||
IMG_MEDIUM, IMG_LARGE, IMG_XLARGE, IMG_XXLARGE, IMG_3XLARGE, IMG_4XLARGE
|
||||
);
|
||||
/** @var string[] */
|
||||
private static $disabled_types_by_default = array(IMG_3XLARGE, IMG_4XLARGE);
|
||||
/** @var DerivativeParams[] */
|
||||
private static $all_type_map = array();
|
||||
/** @var DerivativeParams[] */
|
||||
private static $type_map = array();
|
||||
/** @var DerivativeParams[] */
|
||||
private static $disabled_type_map = array();
|
||||
/** @var DerivativeParams[] */
|
||||
private static $undefined_type_map = array();
|
||||
/** @var WatermarkParams */
|
||||
private static $watermark;
|
||||
/** @var array */
|
||||
public static $custom = array();
|
||||
/** @var int */
|
||||
public static $quality=95;
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
static function get_all_types()
|
||||
{
|
||||
return self::$all_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_all_type_map()
|
||||
{
|
||||
return self::$all_type_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_defined_type_map()
|
||||
{
|
||||
return self::$type_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_disabled_type_map()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (count(self::$disabled_type_map))
|
||||
{
|
||||
return self::$disabled_type_map;
|
||||
}
|
||||
return $conf['disabled_derivatives'] ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_undefined_type_map()
|
||||
{
|
||||
return self::$undefined_type_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams
|
||||
*/
|
||||
static function get_by_type($type)
|
||||
{
|
||||
return self::$all_type_map[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $w
|
||||
* @param int $h
|
||||
* @param float $crop
|
||||
* @param int $minw
|
||||
* @param int $minh
|
||||
* @return DerivativeParams
|
||||
*/
|
||||
static function get_custom($w, $h, $crop=0, $minw=null, $minh=null)
|
||||
{
|
||||
$params = new DerivativeParams( new SizingParams( array($w,$h), $crop, array($minw,$minh)) );
|
||||
self::apply_global($params);
|
||||
|
||||
$key = array();
|
||||
$params->add_url_tokens($key);
|
||||
$key = implode('_',$key);
|
||||
if ( @self::$custom[$key] < time() - 24*3600)
|
||||
{
|
||||
self::$custom[$key] = time();
|
||||
self::save();
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WatermarkParams
|
||||
*/
|
||||
static function get_watermark()
|
||||
{
|
||||
return self::$watermark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads derivative configuration from database or initializes it.
|
||||
*/
|
||||
static function load_from_db()
|
||||
{
|
||||
global $conf;
|
||||
$arr = @unserialize($conf['derivatives']);
|
||||
if (false!==$arr)
|
||||
{
|
||||
self::$type_map = $arr['d'];
|
||||
self::$watermark = @$arr['w'];
|
||||
if (!self::$watermark) self::$watermark = new WatermarkParams();
|
||||
self::$custom = @$arr['c'];
|
||||
if (!self::$custom) self::$custom = array();
|
||||
if (isset($arr['q'])) self::$quality = $arr['q'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$watermark = new WatermarkParams();
|
||||
self::$type_map = self::get_enabled_default_sizes();
|
||||
self::save(false);
|
||||
}
|
||||
|
||||
self::$disabled_type_map = safe_unserialize(self::get_disabled_type_map());
|
||||
if (empty(self::$disabled_type_map))
|
||||
{
|
||||
self::$disabled_type_map = self::get_disabled_default_sizes();
|
||||
self::save_disabled();
|
||||
}
|
||||
|
||||
self::build_maps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WatermarkParams $watermark
|
||||
*/
|
||||
static function set_watermark($watermark)
|
||||
{
|
||||
self::$watermark = $watermark;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ImageStdParams::save()
|
||||
*
|
||||
* @param DerivativeParams[] $map
|
||||
*/
|
||||
static function set_and_save($map)
|
||||
{
|
||||
self::$type_map = $map;
|
||||
self::save(false);
|
||||
self::build_maps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the configuration in database.
|
||||
*/
|
||||
static function save($save_disabled = true)
|
||||
{
|
||||
$ser = serialize( array(
|
||||
'd' => self::$type_map,
|
||||
'q' => self::$quality,
|
||||
'w' => self::$watermark,
|
||||
'c' => self::$custom,
|
||||
) );
|
||||
conf_update_param('derivatives', addslashes($ser) );
|
||||
|
||||
if ($save_disabled)
|
||||
{
|
||||
self::save_disabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the disabled configuration in database.
|
||||
*/
|
||||
static function save_disabled()
|
||||
{
|
||||
if (count(self::$disabled_type_map) > 0)
|
||||
{
|
||||
$disabled = addslashes(serialize(self::$disabled_type_map));
|
||||
conf_update_param('disabled_derivatives', $disabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
$query='DELETE FROM '.CONFIG_TABLE.' WHERE param = \'disabled_derivatives\'';
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
static function set_and_save_disabled($map)
|
||||
{
|
||||
self::$disabled_type_map = $map;
|
||||
self::save_disabled();
|
||||
}
|
||||
|
||||
static function restore_default()
|
||||
{
|
||||
self::$type_map = self::get_enabled_default_sizes();
|
||||
self::$disabled_type_map = self::get_disabled_default_sizes();
|
||||
self::save();
|
||||
self::build_maps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_default_sizes()
|
||||
{
|
||||
$arr = array(
|
||||
IMG_SQUARE => new DerivativeParams( SizingParams::square(120,120) ),
|
||||
IMG_THUMB => new DerivativeParams( SizingParams::classic(144,144) ),
|
||||
IMG_XXSMALL => new DerivativeParams( SizingParams::classic(240,240) ),
|
||||
IMG_XSMALL => new DerivativeParams( SizingParams::classic(432,324) ),
|
||||
IMG_SMALL => new DerivativeParams( SizingParams::classic(576,432) ),
|
||||
IMG_MEDIUM => new DerivativeParams( SizingParams::classic(792,594) ),
|
||||
IMG_LARGE => new DerivativeParams( SizingParams::classic(1008,756) ),
|
||||
IMG_XLARGE => new DerivativeParams( SizingParams::classic(1224,918) ),
|
||||
IMG_XXLARGE => new DerivativeParams( SizingParams::classic(1656,1242) ),
|
||||
IMG_3XLARGE => new DerivativeParams( SizingParams::classic(2232,1674) ),
|
||||
IMG_4XLARGE => new DerivativeParams( SizingParams::classic(3000,2250) ),
|
||||
);
|
||||
$now = time();
|
||||
foreach($arr as $params)
|
||||
{
|
||||
$params->last_mod_time = $now;
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_enabled_default_sizes()
|
||||
{
|
||||
$default_sizes = self::get_default_sizes();
|
||||
foreach (self::$disabled_types_by_default as $type)
|
||||
{
|
||||
unset($default_sizes[$type]);
|
||||
}
|
||||
return $default_sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DerivativeParams[]
|
||||
*/
|
||||
static function get_disabled_default_sizes()
|
||||
{
|
||||
$all = self::get_default_sizes();
|
||||
$disabled_sizes = array_intersect_key($all, array_flip(self::$disabled_types_by_default));
|
||||
return $disabled_sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute 'apply_watermark'
|
||||
*
|
||||
* @param DerivativeParams $params
|
||||
*/
|
||||
static function apply_global($params)
|
||||
{
|
||||
$params->use_watermark = !empty(self::$watermark->file) &&
|
||||
(self::$watermark->min_size[0]<=$params->sizing->ideal_size[0]
|
||||
or self::$watermark->min_size[1]<=$params->sizing->ideal_size[1] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build 'type_map', 'all_type_map' and 'undefined_type_map'.
|
||||
*/
|
||||
private static function build_maps()
|
||||
{
|
||||
foreach (self::$type_map as $type=>$params)
|
||||
{
|
||||
$params->type = $type;
|
||||
self::apply_global($params);
|
||||
}
|
||||
self::$all_type_map = self::$type_map;
|
||||
|
||||
for ($i=0; $i<count(self::$all_types); $i++)
|
||||
{
|
||||
$tocheck = self::$all_types[$i];
|
||||
if (!isset(self::$type_map[$tocheck]))
|
||||
{
|
||||
for ($j=$i-1; $j>=0; $j--)
|
||||
{
|
||||
$target = self::$all_types[$j];
|
||||
if (isset(self::$type_map[$target]))
|
||||
{
|
||||
self::$all_type_map[$tocheck] = self::$type_map[$target];
|
||||
self::$undefined_type_map[$tocheck] = $target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,682 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class provides functions for converting CSS styles into inline style attributes in your HTML code.
|
||||
*
|
||||
* For more information, please see the README.md file.
|
||||
*
|
||||
* @author Cameron Brooks
|
||||
* @author Jaime Prado
|
||||
*/
|
||||
class Emogrifier {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const ENCODING = 'UTF-8';
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const CACHE_KEY_CSS = 0;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const CACHE_KEY_SELECTOR = 1;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
const CACHE_KEY_XPATH = 2;
|
||||
|
||||
/**
|
||||
* for calculating nth-of-type and nth-child selectors
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const INDEX = 0;
|
||||
|
||||
/**
|
||||
* for calculating nth-of-type and nth-child selectors
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const MULTIPLIER = 1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const ID_ATTRIBUTE_MATCHER = '/(\\w+)?\\#([\\w\\-]+)/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CLASS_ATTRIBUTE_MATCHER = '/(\\w+|[\\*\\]])?((\\.[\\w\\-]+)+)/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $html = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $css = '';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private $unprocessableHtmlTags = array('wbr');
|
||||
|
||||
/**
|
||||
* @var array<array>
|
||||
*/
|
||||
private $caches = array(
|
||||
self::CACHE_KEY_CSS => array(),
|
||||
self::CACHE_KEY_SELECTOR => array(),
|
||||
self::CACHE_KEY_XPATH => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* the visited nodes with the XPath paths as array keys
|
||||
*
|
||||
* @var array<\DOMNode>
|
||||
*/
|
||||
private $visitedNodes = array();
|
||||
|
||||
/**
|
||||
* the styles to apply to the nodes with the XPath paths as array keys for the outer array and the attribute names/values
|
||||
* as key/value pairs for the inner array
|
||||
*
|
||||
* @var array<array><string>
|
||||
*/
|
||||
private $styleAttributesForNodes = array();
|
||||
|
||||
/**
|
||||
* This attribute applies to the case where you want to preserve your original text encoding.
|
||||
*
|
||||
* By default, emogrifier translates your text into HTML entities for two reasons:
|
||||
*
|
||||
* 1. Because of client incompatibilities, it is better practice to send out HTML entities rather than unicode over email.
|
||||
*
|
||||
* 2. It translates any illegal XML characters that DOMDocument cannot work with.
|
||||
*
|
||||
* If you would like to preserve your original encoding, set this attribute to TRUE.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $preserveEncoding = FALSE;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param string $html the HTML to emogrify, must be UTF-8-encoded
|
||||
* @param string $css the CSS to merge, must be UTF-8-encoded
|
||||
*/
|
||||
public function __construct($html = '', $css = '') {
|
||||
$this->setHtml($html);
|
||||
$this->setCss($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* The destructor.
|
||||
*/
|
||||
public function __destruct() {
|
||||
$this->purgeVisitedNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTML to emogrify.
|
||||
*
|
||||
* @param string $html the HTML to emogrify, must be UTF-8-encoded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setHtml($html = '') {
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS to merge with the HTML.
|
||||
*
|
||||
* @param string $css the CSS to merge, must be UTF-8-encoded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCss($css = '') {
|
||||
$this->css = $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all caches.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function clearAllCaches() {
|
||||
$this->clearCache(self::CACHE_KEY_CSS);
|
||||
$this->clearCache(self::CACHE_KEY_SELECTOR);
|
||||
$this->clearCache(self::CACHE_KEY_XPATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a single cache by key.
|
||||
*
|
||||
* @param integer $key the cache key, must be CACHE_KEY_CSS, CACHE_KEY_SELECTOR or CACHE_KEY_XPATH
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function clearCache($key) {
|
||||
$allowedCacheKeys = array(self::CACHE_KEY_CSS, self::CACHE_KEY_SELECTOR, self::CACHE_KEY_XPATH);
|
||||
if (!in_array($key, $allowedCacheKeys, TRUE)) {
|
||||
throw new InvalidArgumentException('Invalid cache key: ' . $key, 1391822035);
|
||||
}
|
||||
|
||||
$this->caches[$key] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges the visited nodes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function purgeVisitedNodes() {
|
||||
$this->visitedNodes = array();
|
||||
$this->styleAttributesForNodes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a tag for removal.
|
||||
*
|
||||
* There are some HTML tags that DOMDocument cannot process, and it will throw an error if it encounters them.
|
||||
* In particular, DOMDocument will complain if you try to use HTML5 tags in an XHTML document.
|
||||
*
|
||||
* Note: The tags will not be removed if they have any content.
|
||||
*
|
||||
* @param string $tagName the tag name, e.g., "p"
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addUnprocessableHtmlTag($tagName) {
|
||||
$this->unprocessableHtmlTags[] = $tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a tag from the removal list.
|
||||
*
|
||||
* @param string $tagName the tag name, e.g., "p"
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeUnprocessableHtmlTag($tagName) {
|
||||
$key = array_search($tagName, $this->unprocessableHtmlTags, TRUE);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->unprocessableHtmlTags[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the CSS you submit to the HTML you submit.
|
||||
*
|
||||
* This method places the CSS inline.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function emogrify() {
|
||||
if ($this->html === '') {
|
||||
throw new BadMethodCallException('Please set some HTML first before calling emogrify.', 1390393096);
|
||||
}
|
||||
|
||||
$xmlDocument = $this->createXmlDocument();
|
||||
$xpath = new DOMXPath($xmlDocument);
|
||||
$this->clearAllCaches();
|
||||
|
||||
// before be begin processing the CSS file, parse the document and normalize all existing CSS attributes (changes 'DISPLAY: none' to 'display: none');
|
||||
// we wouldn't have to do this if DOMXPath supported XPath 2.0.
|
||||
// also store a reference of nodes with existing inline styles so we don't overwrite them
|
||||
$this->purgeVisitedNodes();
|
||||
|
||||
$nodesWithStyleAttributes = $xpath->query('//*[@style]');
|
||||
if ($nodesWithStyleAttributes !== FALSE) {
|
||||
$callback = function($m) { return strtolower($m[0]); };
|
||||
|
||||
/** @var $nodeWithStyleAttribute \DOMNode */
|
||||
foreach ($nodesWithStyleAttributes as $node) {
|
||||
$normalizedOriginalStyle = preg_replace_callback(
|
||||
'/[A-z\\-]+(?=\\:)/S',
|
||||
$callback,
|
||||
$node->getAttribute('style')
|
||||
);
|
||||
|
||||
// in order to not overwrite existing style attributes in the HTML, we have to save the original HTML styles
|
||||
$nodePath = $node->getNodePath();
|
||||
if (!isset($this->styleAttributesForNodes[$nodePath])) {
|
||||
$this->styleAttributesForNodes[$nodePath] = $this->parseCssDeclarationBlock($normalizedOriginalStyle);
|
||||
$this->visitedNodes[$nodePath] = $node;
|
||||
}
|
||||
|
||||
$node->setAttribute('style', $normalizedOriginalStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// grab any existing style blocks from the html and append them to the existing CSS
|
||||
// (these blocks should be appended so as to have precedence over conflicting styles in the existing CSS)
|
||||
$css = $this->css;
|
||||
$styleNodes = $xpath->query('//style');
|
||||
if ($styleNodes !== FALSE) {
|
||||
/** @var $styleNode \DOMNode */
|
||||
foreach ($styleNodes as $styleNode) {
|
||||
// append the css
|
||||
$css .= "\n\n" . $styleNode->nodeValue;
|
||||
// remove the <style> node
|
||||
$styleNode->parentNode->removeChild($styleNode);
|
||||
}
|
||||
}
|
||||
|
||||
// filter the CSS
|
||||
$search = array(
|
||||
// get rid of css comment code
|
||||
'/\\/\\*.*\\*\\//sU',
|
||||
// strip out any import directives
|
||||
'/^\\s*@import\\s[^;]+;/misU',
|
||||
// strip any empty media enclosures
|
||||
'/^\\s*@media\\s[^{]+{\\s*}/misU',
|
||||
// strip out all media rules that are not 'screen' or 'all' (these don't apply to email)
|
||||
'/^\\s*@media\\s+((aural|braille|embossed|handheld|print|projection|speech|tty|tv)\\s*,*\\s*)+{.*}\\s*}/misU',
|
||||
// get rid of remaining media type rules
|
||||
'/^\\s*@media\\s[^{]+{(.*})\\s*}/misU',
|
||||
);
|
||||
|
||||
$replace = array(
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'\\1',
|
||||
);
|
||||
|
||||
$css = preg_replace($search, $replace, $css);
|
||||
|
||||
$cssKey = md5($css);
|
||||
if (!isset($this->caches[self::CACHE_KEY_CSS][$cssKey])) {
|
||||
// process the CSS file for selectors and definitions
|
||||
preg_match_all('/(?:^|[^{}])\\s*([^{]+){([^}]*)}/mis', $css, $matches, PREG_SET_ORDER);
|
||||
|
||||
$allSelectors = array();
|
||||
foreach ($matches as $key => $selectorString) {
|
||||
// if there is a blank definition, skip
|
||||
if (!strlen(trim($selectorString[2]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// else split by commas and duplicate attributes so we can sort by selector precedence
|
||||
$selectors = explode(',', $selectorString[1]);
|
||||
foreach ($selectors as $selector) {
|
||||
// don't process pseudo-elements and behavioral (dynamic) pseudo-classes; ONLY allow structural pseudo-classes
|
||||
if (strpos($selector, ':') !== FALSE && !preg_match('/:\\S+\\-(child|type)\\(/i', $selector)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$allSelectors[] = array('selector' => trim($selector),
|
||||
'attributes' => trim($selectorString[2]),
|
||||
// keep track of where it appears in the file, since order is important
|
||||
'line' => $key,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// now sort the selectors by precedence
|
||||
usort($allSelectors, array($this,'sortBySelectorPrecedence'));
|
||||
|
||||
$this->caches[self::CACHE_KEY_CSS][$cssKey] = $allSelectors;
|
||||
}
|
||||
|
||||
foreach ($this->caches[self::CACHE_KEY_CSS][$cssKey] as $value) {
|
||||
// query the body for the xpath selector
|
||||
$nodesMatchingCssSelectors = $xpath->query($this->translateCssToXpath(trim($value['selector'])));
|
||||
|
||||
/** @var $node \DOMNode */
|
||||
foreach ($nodesMatchingCssSelectors as $node) {
|
||||
// if it has a style attribute, get it, process it, and append (overwrite) new stuff
|
||||
if ($node->hasAttribute('style')) {
|
||||
// break it up into an associative array
|
||||
$oldStyleDeclarations = $this->parseCssDeclarationBlock($node->getAttribute('style'));
|
||||
$newStyleDeclarations = $this->parseCssDeclarationBlock($value['attributes']);
|
||||
|
||||
// new styles overwrite the old styles (not technically accurate, but close enough)
|
||||
$combinedArray = array_merge($oldStyleDeclarations, $newStyleDeclarations);
|
||||
$style = '';
|
||||
foreach ($combinedArray as $attributeName => $attributeValue) {
|
||||
$style .= (strtolower($attributeName) . ':' . $attributeValue . ';');
|
||||
}
|
||||
} else {
|
||||
// otherwise create a new style
|
||||
$style = trim($value['attributes']);
|
||||
}
|
||||
$node->setAttribute('style', $style);
|
||||
}
|
||||
}
|
||||
|
||||
// now iterate through the nodes that contained inline styles in the original HTML
|
||||
foreach ($this->styleAttributesForNodes as $nodePath => $styleAttributesForNode) {
|
||||
$node = $this->visitedNodes[$nodePath];
|
||||
$currentStyleAttributes = $this->parseCssDeclarationBlock($node->getAttribute('style'));
|
||||
|
||||
$combinedArray = array_merge($currentStyleAttributes, $styleAttributesForNode);
|
||||
$style = '';
|
||||
foreach ($combinedArray as $attributeName => $attributeValue) {
|
||||
$style .= (strtolower($attributeName) . ':' . $attributeValue . ';');
|
||||
}
|
||||
|
||||
$node->setAttribute('style', $style);
|
||||
}
|
||||
|
||||
// This removes styles from your email that contain display:none.
|
||||
// We need to look for display:none, but we need to do a case-insensitive search. Since DOMDocument only supports XPath 1.0,
|
||||
// lower-case() isn't available to us. We've thus far only set attributes to lowercase, not attribute values. Consequently, we need
|
||||
// to translate() the letters that would be in 'NONE' ("NOE") to lowercase.
|
||||
$nodesWithStyleDisplayNone = $xpath->query('//*[contains(translate(translate(@style," ",""),"NOE","noe"),"display:none")]');
|
||||
// The checks on parentNode and is_callable below ensure that if we've deleted the parent node,
|
||||
// we don't try to call removeChild on a nonexistent child node
|
||||
if ($nodesWithStyleDisplayNone->length > 0) {
|
||||
/** @var $node \DOMNode */
|
||||
foreach ($nodesWithStyleDisplayNone as $node) {
|
||||
if ($node->parentNode && is_callable(array($node->parentNode,'removeChild'))) {
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->preserveEncoding) {
|
||||
return mb_convert_encoding($xmlDocument->saveHTML(), self::ENCODING, 'HTML-ENTITIES');
|
||||
} else {
|
||||
return $xmlDocument->saveHTML();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOMDocument instance with the current HTML.
|
||||
*
|
||||
* @return \DOMDocument
|
||||
*/
|
||||
private function createXmlDocument() {
|
||||
$xmlDocument = new DOMDocument;
|
||||
$xmlDocument->encoding = self::ENCODING;
|
||||
$xmlDocument->strictErrorChecking = FALSE;
|
||||
$xmlDocument->formatOutput = TRUE;
|
||||
$libxmlState = libxml_use_internal_errors(TRUE);
|
||||
$xmlDocument->loadHTML($this->getUnifiedHtml());
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($libxmlState);
|
||||
$xmlDocument->normalizeDocument();
|
||||
|
||||
return $xmlDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML with the non-ASCII characters converts into HTML entities and the unprocessable HTML tags removed.
|
||||
*
|
||||
* @return string the unified HTML
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
private function getUnifiedHtml() {
|
||||
if (!empty($this->unprocessableHtmlTags)) {
|
||||
$unprocessableHtmlTags = implode('|', $this->unprocessableHtmlTags);
|
||||
$bodyWithoutUnprocessableTags = preg_replace('/<\\/?(' . $unprocessableHtmlTags . ')[^>]*>/i', '', $this->html);
|
||||
} else {
|
||||
$bodyWithoutUnprocessableTags = $this->html;
|
||||
}
|
||||
|
||||
return mb_convert_encoding($bodyWithoutUnprocessableTags, 'HTML-ENTITIES', self::ENCODING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function sortBySelectorPrecedence(array $a, array $b) {
|
||||
$precedenceA = $this->getCssSelectorPrecedence($a['selector']);
|
||||
$precedenceB = $this->getCssSelectorPrecedence($b['selector']);
|
||||
|
||||
// We want these sorted in ascending order so selectors with lesser precedence get processed first and
|
||||
// selectors with greater precedence get sorted last.
|
||||
// The parenthesis around the -1 are necessary to avoid a PHP_CodeSniffer warning about missing spaces around
|
||||
// arithmetic operators.
|
||||
// @see http://forge.typo3.org/issues/55605
|
||||
$precedenceForEquals = ($a['line'] < $b['line'] ? (-1) : 1);
|
||||
$precedenceForNotEquals = ($precedenceA < $precedenceB ? (-1) : 1);
|
||||
return ($precedenceA === $precedenceB) ? $precedenceForEquals : $precedenceForNotEquals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $selector
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function getCssSelectorPrecedence($selector) {
|
||||
$selectorKey = md5($selector);
|
||||
if (!isset($this->caches[self::CACHE_KEY_SELECTOR][$selectorKey])) {
|
||||
$precedence = 0;
|
||||
$value = 100;
|
||||
// ids: worth 100, classes: worth 10, elements: worth 1
|
||||
$search = array('\\#','\\.','');
|
||||
|
||||
foreach ($search as $s) {
|
||||
if (trim($selector == '')) {
|
||||
break;
|
||||
}
|
||||
$number = 0;
|
||||
$selector = preg_replace('/' . $s . '\\w+/', '', $selector, -1, $number);
|
||||
$precedence += ($value * $number);
|
||||
$value /= 10;
|
||||
}
|
||||
$this->caches[self::CACHE_KEY_SELECTOR][$selectorKey] = $precedence;
|
||||
}
|
||||
|
||||
return $this->caches[self::CACHE_KEY_SELECTOR][$selectorKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Right now, we support all CSS 1 selectors and most CSS2/3 selectors.
|
||||
*
|
||||
* @see http://plasmasturm.org/log/444/
|
||||
*
|
||||
* @param string $cssSelector
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function translateCssToXpath($cssSelector) {
|
||||
$cssSelector = trim($cssSelector);
|
||||
$xpathKey = md5($cssSelector);
|
||||
if (!isset($this->caches[self::CACHE_KEY_XPATH][$xpathKey])) {
|
||||
// returns an Xpath selector
|
||||
$search = array(
|
||||
// Matches any element that is a child of parent.
|
||||
'/\\s+>\\s+/',
|
||||
// Matches any element that is an adjacent sibling.
|
||||
'/\\s+\\+\\s+/',
|
||||
// Matches any element that is a descendant of an parent element element.
|
||||
'/\\s+/',
|
||||
// first-child pseudo-selector
|
||||
'/([^\\/]+):first-child/i',
|
||||
// last-child pseudo-selector
|
||||
'/([^\\/]+):last-child/i',
|
||||
// Matches element with attribute
|
||||
'/(\\w)\\[(\\w+)\\]/',
|
||||
// Matches element with EXACT attribute
|
||||
'/(\\w)\\[(\\w+)\\=[\'"]?(\\w+)[\'"]?\\]/',
|
||||
);
|
||||
$replace = array(
|
||||
'/',
|
||||
'/following-sibling::*[1]/self::',
|
||||
'//',
|
||||
'*[1]/self::\\1',
|
||||
'*[last()]/self::\\1',
|
||||
'\\1[@\\2]',
|
||||
'\\1[@\\2="\\3"]',
|
||||
);
|
||||
|
||||
$cssSelector = '//' . preg_replace($search, $replace, $cssSelector);
|
||||
|
||||
$cssSelector = preg_replace_callback(self::ID_ATTRIBUTE_MATCHER, array($this, 'matchIdAttributes'), $cssSelector);
|
||||
$cssSelector = preg_replace_callback(self::CLASS_ATTRIBUTE_MATCHER, array($this, 'matchClassAttributes'), $cssSelector);
|
||||
|
||||
// Advanced selectors are going to require a bit more advanced emogrification.
|
||||
// When we required PHP 5.3, we could do this with closures.
|
||||
$cssSelector = preg_replace_callback(
|
||||
'/([^\\/]+):nth-child\\(\s*(odd|even|[+\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
|
||||
array($this, 'translateNthChild'), $cssSelector
|
||||
);
|
||||
$cssSelector = preg_replace_callback(
|
||||
'/([^\\/]+):nth-of-type\\(\s*(odd|even|[+\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
|
||||
array($this, 'translateNthOfType'), $cssSelector
|
||||
);
|
||||
|
||||
$this->caches[self::CACHE_KEY_SELECTOR][$xpathKey] = $cssSelector;
|
||||
}
|
||||
return $this->caches[self::CACHE_KEY_SELECTOR][$xpathKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function matchIdAttributes(array $match) {
|
||||
return (strlen($match[1]) ? $match[1] : '*') . '[@id="' . $match[2] . '"]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function matchClassAttributes(array $match) {
|
||||
return (strlen($match[1]) ? $match[1] : '*') . '[contains(concat(" ",@class," "),concat(" ","' .
|
||||
implode(
|
||||
'"," "))][contains(concat(" ",@class," "),concat(" ","',
|
||||
explode('.', substr($match[2], 1))
|
||||
) . '"," "))]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function translateNthChild(array $match) {
|
||||
$result = $this->parseNth($match);
|
||||
|
||||
if (isset($result[self::MULTIPLIER])) {
|
||||
if ($result[self::MULTIPLIER] < 0) {
|
||||
$result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
|
||||
return sprintf('*[(last() - position()) mod %u = %u]/self::%s', $result[self::MULTIPLIER], $result[self::INDEX], $match[1]);
|
||||
} else {
|
||||
return sprintf('*[position() mod %u = %u]/self::%s', $result[self::MULTIPLIER], $result[self::INDEX], $match[1]);
|
||||
}
|
||||
} else {
|
||||
return sprintf('*[%u]/self::%s', $result[self::INDEX], $match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function translateNthOfType(array $match) {
|
||||
$result = $this->parseNth($match);
|
||||
|
||||
if (isset($result[self::MULTIPLIER])) {
|
||||
if ($result[self::MULTIPLIER] < 0) {
|
||||
$result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
|
||||
return sprintf('%s[(last() - position()) mod %u = %u]', $match[1], $result[self::MULTIPLIER], $result[self::INDEX]);
|
||||
} else {
|
||||
return sprintf('%s[position() mod %u = %u]', $match[1], $result[self::MULTIPLIER], $result[self::INDEX]);
|
||||
}
|
||||
} else {
|
||||
return sprintf('%s[%u]', $match[1], $result[self::INDEX]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $match
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseNth(array $match) {
|
||||
if (in_array(strtolower($match[2]), array('even','odd'))) {
|
||||
$index = strtolower($match[2]) == 'even' ? 0 : 1;
|
||||
return array(self::MULTIPLIER => 2, self::INDEX => $index);
|
||||
} elseif (stripos($match[2], 'n') === FALSE) {
|
||||
// if there is a multiplier
|
||||
$index = intval(str_replace(' ', '', $match[2]));
|
||||
return array(self::INDEX => $index);
|
||||
} else {
|
||||
if (isset($match[3])) {
|
||||
$multipleTerm = str_replace($match[3], '', $match[2]);
|
||||
$index = intval(str_replace(' ', '', $match[3]));
|
||||
} else {
|
||||
$multipleTerm = $match[2];
|
||||
$index = 0;
|
||||
}
|
||||
|
||||
$multiplier = str_ireplace('n', '', $multipleTerm);
|
||||
|
||||
if (!strlen($multiplier)) {
|
||||
$multiplier = 1;
|
||||
} elseif ($multiplier == 0) {
|
||||
return array(self::INDEX => $index);
|
||||
} else {
|
||||
$multiplier = intval($multiplier);
|
||||
}
|
||||
|
||||
while ($index < 0) {
|
||||
$index += abs($multiplier);
|
||||
}
|
||||
|
||||
return array(self::MULTIPLIER => $multiplier, self::INDEX => $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSS declaration block into property name/value pairs.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The declaration block
|
||||
*
|
||||
* "color: #000; font-weight: bold;"
|
||||
*
|
||||
* will be parsed into the following array:
|
||||
*
|
||||
* "color" => "#000"
|
||||
* "font-weight" => "bold"
|
||||
*
|
||||
* @param string $cssDeclarationBlock the CSS declaration block without the curly braces, may be empty
|
||||
*
|
||||
* @return array the CSS declarations with the property names as array keys and the property values as array values
|
||||
*/
|
||||
private function parseCssDeclarationBlock($cssDeclarationBlock) {
|
||||
$properties = array();
|
||||
|
||||
$declarations = explode(';', $cssDeclarationBlock);
|
||||
foreach ($declarations as $declaration) {
|
||||
$matches = array();
|
||||
if (!preg_match('/ *([a-z\-]+) *: *([^;]+) */', $declaration, $matches)) {
|
||||
continue;
|
||||
}
|
||||
$propertyName = $matches[1];
|
||||
$propertyValue = $matches[2];
|
||||
$properties[$propertyName] = $propertyValue;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
// $filter['enabled']: Filter is enabled
|
||||
// $filter['recent_period']: Recent period used to computed filter data
|
||||
// $filter['categories']: Computed data of filtered categories
|
||||
// $filter['visible_categories']:
|
||||
// List of visible categories (count(visible) < count(forbidden) more often)
|
||||
// $filter['visible_images']: List of visible images
|
||||
|
||||
if (!get_filter_page_value('cancel'))
|
||||
{
|
||||
if (isset($_GET['filter']))
|
||||
{
|
||||
$filter['matches'] = array();
|
||||
$filter['enabled'] =
|
||||
preg_match('/^start-recent-(\d+)$/', $_GET['filter'], $filter['matches']) === 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter['enabled'] = pwg_get_session_var('filter_enabled', false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter['enabled'] = false;
|
||||
}
|
||||
|
||||
if ($filter['enabled'])
|
||||
{
|
||||
$filter_key = pwg_get_session_var('filter_check_key', array('user'=>0,'recent_period'=>-1, 'time'=>0, 'date'=> '') );
|
||||
|
||||
if (isset($filter['matches']))
|
||||
{
|
||||
$filter['recent_period'] = $filter['matches'][1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter['recent_period'] = $filter_key['recent_period']>0 ? $filter_key['recent_period'] : $user['recent_period'];
|
||||
}
|
||||
|
||||
if (
|
||||
// New filter
|
||||
!pwg_get_session_var('filter_enabled', false) or
|
||||
// Cache data updated
|
||||
$filter_key['time'] <= $user['cache_update_time'] or
|
||||
// Date, period, user are changed
|
||||
$filter_key['user'] != $user['id'] or
|
||||
$filter_key['recent_period'] != $filter['recent_period'] or
|
||||
$filter_key['date'] != date('Ymd')
|
||||
)
|
||||
{
|
||||
// Need to compute dats
|
||||
$filter_key = array(
|
||||
'user'=>(int)$user['id'],'recent_period'=>(int)$filter['recent_period'], 'time'=>time(), 'date'=> date('Ymd')
|
||||
);
|
||||
|
||||
$filter['categories'] = get_computed_categories($user, (int)$filter['recent_period']);
|
||||
|
||||
$filter['visible_categories'] = implode(',', array_keys($filter['categories']));
|
||||
if (empty($filter['visible_categories']))
|
||||
{
|
||||
// Must be not empty
|
||||
$filter['visible_categories'] = -1;
|
||||
}
|
||||
|
||||
$query ='
|
||||
SELECT
|
||||
distinct image_id
|
||||
FROM '.
|
||||
IMAGE_CATEGORY_TABLE.' INNER JOIN '.IMAGES_TABLE.' ON image_id = id
|
||||
WHERE ';
|
||||
if (!empty($filter['visible_categories']))
|
||||
{
|
||||
$query.= '
|
||||
category_id IN ('.$filter['visible_categories'].') and';
|
||||
}
|
||||
$query.= '
|
||||
date_available >= '.pwg_db_get_recent_period_expression($filter['recent_period']);
|
||||
|
||||
$filter['visible_images'] = implode(',', array_from_query($query, 'image_id'));
|
||||
|
||||
if (empty($filter['visible_images']))
|
||||
{
|
||||
// Must be not empty
|
||||
$filter['visible_images'] = -1;
|
||||
}
|
||||
|
||||
// Save filter data on session
|
||||
pwg_set_session_var('filter_enabled', $filter['enabled']);
|
||||
pwg_set_session_var('filter_check_key', $filter_key);
|
||||
pwg_set_session_var('filter_categories', serialize($filter['categories']));
|
||||
pwg_set_session_var('filter_visible_categories', $filter['visible_categories']);
|
||||
pwg_set_session_var('filter_visible_images', $filter['visible_images']);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read only data
|
||||
$filter['categories'] = unserialize(pwg_get_session_var('filter_categories', serialize(array())));
|
||||
$filter['visible_categories'] = pwg_get_session_var('filter_visible_categories', '');
|
||||
$filter['visible_images'] = pwg_get_session_var('filter_visible_images', '');
|
||||
}
|
||||
unset($filter_key);
|
||||
if (get_filter_page_value('add_notes'))
|
||||
{
|
||||
$header_notes[] = l10n_dec(
|
||||
'Photos posted within the last %d day.', 'Photos posted within the last %d days.',
|
||||
$filter['recent_period']
|
||||
);
|
||||
}
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_filter.inc.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pwg_get_session_var('filter_enabled', false))
|
||||
{
|
||||
pwg_unset_session_var('filter_enabled');
|
||||
pwg_unset_session_var('filter_check_key');
|
||||
pwg_unset_session_var('filter_categories');
|
||||
pwg_unset_session_var('filter_visible_categories');
|
||||
pwg_unset_session_var('filter_visible_images');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\calendar
|
||||
*/
|
||||
|
||||
/** URL keyword for list view */
|
||||
define('CAL_VIEW_LIST', 'list');
|
||||
/** URL keyword for calendar view */
|
||||
define('CAL_VIEW_CALENDAR', 'calendar');
|
||||
|
||||
|
||||
/**
|
||||
* Initialize _$page_ and _$template_ vars for calendar view.
|
||||
*/
|
||||
function initialize_calendar()
|
||||
{
|
||||
global $page, $conf, $user, $template, $persistent_cache, $filter;
|
||||
|
||||
//------------------ initialize the condition on items to take into account ---
|
||||
$inner_sql = ' FROM ' . IMAGES_TABLE;
|
||||
|
||||
if ($page['section']=='categories')
|
||||
{ // we will regenerate the items by including subcats elements
|
||||
$page['items'] = array();
|
||||
$inner_sql .= '
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON id = image_id';
|
||||
|
||||
if ( isset($page['category']) )
|
||||
{
|
||||
$sub_ids = array_diff(
|
||||
get_subcat_ids(array($page['category']['id'])),
|
||||
explode(',', $user['forbidden_categories'])
|
||||
);
|
||||
|
||||
if (empty($sub_ids))
|
||||
{
|
||||
return; // nothing to do
|
||||
}
|
||||
$inner_sql .= '
|
||||
WHERE category_id IN ('.implode(',',$sub_ids).')';
|
||||
$inner_sql .= '
|
||||
'.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
'AND', false
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$inner_sql .= '
|
||||
'.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
'WHERE', true
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( empty($page['items']) )
|
||||
{
|
||||
return; // nothing to do
|
||||
}
|
||||
$inner_sql .= '
|
||||
WHERE id IN (' . implode(',',$page['items']) .')';
|
||||
}
|
||||
|
||||
//-------------------------------------- initialize the calendar parameters ---
|
||||
pwg_debug('start initialize_calendar');
|
||||
|
||||
$fields = array(
|
||||
// Created
|
||||
'created' => array(
|
||||
'label' => l10n('Creation date'),
|
||||
),
|
||||
// Posted
|
||||
'posted' => array(
|
||||
'label' => l10n('Post date'),
|
||||
),
|
||||
);
|
||||
|
||||
$styles = array(
|
||||
// Monthly style
|
||||
'monthly' => array(
|
||||
'include' => 'calendar_monthly.class.php',
|
||||
'view_calendar' => true,
|
||||
'classname' => 'CalendarMonthly',
|
||||
),
|
||||
// Weekly style
|
||||
'weekly' => array(
|
||||
'include' => 'calendar_weekly.class.php',
|
||||
'view_calendar' => false,
|
||||
'classname' => 'CalendarWeekly',
|
||||
),
|
||||
);
|
||||
|
||||
$views = array(CAL_VIEW_LIST,CAL_VIEW_CALENDAR);
|
||||
|
||||
// Retrieve calendar field
|
||||
isset( $fields[ $page['chronology_field'] ] ) or fatal_error('bad chronology field');
|
||||
|
||||
// Retrieve style
|
||||
if ( !isset( $styles[ $page['chronology_style'] ] ) )
|
||||
{
|
||||
$page['chronology_style'] = 'monthly';
|
||||
}
|
||||
$cal_style = $page['chronology_style'];
|
||||
$classname = $styles[$cal_style]['classname'];
|
||||
|
||||
include(PHPWG_ROOT_PATH.'include/'. $styles[$cal_style]['include']);
|
||||
$calendar = new $classname();
|
||||
|
||||
// Retrieve view
|
||||
|
||||
if ( !isset($page['chronology_view']) or
|
||||
!in_array( $page['chronology_view'], $views ) )
|
||||
{
|
||||
$page['chronology_view'] = CAL_VIEW_LIST;
|
||||
}
|
||||
|
||||
if ( CAL_VIEW_CALENDAR==$page['chronology_view'] and
|
||||
!$styles[$cal_style]['view_calendar'] )
|
||||
{
|
||||
|
||||
$page['chronology_view'] = CAL_VIEW_LIST;
|
||||
}
|
||||
|
||||
// perform a sanity check on $requested
|
||||
if (!isset($page['chronology_date']))
|
||||
{
|
||||
$page['chronology_date'] = array();
|
||||
}
|
||||
while ( count($page['chronology_date']) > 3)
|
||||
{
|
||||
array_pop($page['chronology_date']);
|
||||
}
|
||||
|
||||
$any_count = 0;
|
||||
for ($i = 0; $i < count($page['chronology_date']); $i++)
|
||||
{
|
||||
if ($page['chronology_date'][$i] == 'any')
|
||||
{
|
||||
if ($page['chronology_view'] == CAL_VIEW_CALENDAR)
|
||||
{// we dont allow any in calendar view
|
||||
while ($i < count($page['chronology_date']))
|
||||
{
|
||||
array_pop($page['chronology_date']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$any_count++;
|
||||
}
|
||||
elseif ($page['chronology_date'][$i] == '')
|
||||
{
|
||||
while ($i < count($page['chronology_date']))
|
||||
{
|
||||
array_pop($page['chronology_date']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['chronology_date'][$i] = (int)$page['chronology_date'][$i];
|
||||
}
|
||||
}
|
||||
if ($any_count == 3)
|
||||
{
|
||||
array_pop($page['chronology_date']);
|
||||
}
|
||||
|
||||
$calendar->initialize($inner_sql);
|
||||
|
||||
//echo ('<pre>'. var_export($calendar, true) . '</pre>');
|
||||
|
||||
$must_show_list = true; // true until calendar generates its own display
|
||||
if (script_basename() != 'picture') // basename without file extention
|
||||
{
|
||||
if ($calendar->generate_category_content())
|
||||
{
|
||||
$page['items'] = array();
|
||||
$must_show_list = false;
|
||||
}
|
||||
|
||||
$page['comment'] = '';
|
||||
$template->assign('FILE_CHRONOLOGY_VIEW', 'month_calendar.tpl');
|
||||
|
||||
foreach ($styles as $style => $style_data)
|
||||
{
|
||||
foreach ($views as $view)
|
||||
{
|
||||
if ( $style_data['view_calendar'] or $view != CAL_VIEW_CALENDAR)
|
||||
{
|
||||
$selected = false;
|
||||
|
||||
if ($style!=$cal_style)
|
||||
{
|
||||
$chronology_date = array();
|
||||
if ( isset($page['chronology_date'][0]) )
|
||||
{
|
||||
$chronology_date[] = $page['chronology_date'][0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$chronology_date = $page['chronology_date'];
|
||||
}
|
||||
$url = duplicate_index_url(
|
||||
array(
|
||||
'chronology_style' => $style,
|
||||
'chronology_view' => $view,
|
||||
'chronology_date' => $chronology_date,
|
||||
)
|
||||
);
|
||||
|
||||
if ($style==$cal_style and $view==$page['chronology_view'] )
|
||||
{
|
||||
$selected = true;
|
||||
}
|
||||
|
||||
$template->append(
|
||||
'chronology_views',
|
||||
array(
|
||||
'VALUE' => $url,
|
||||
'CONTENT' => l10n('chronology_'.$style.'_'.$view),
|
||||
'SELECTED' => $selected,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$url = duplicate_index_url(
|
||||
array(), array('start', 'chronology_date')
|
||||
);
|
||||
$calendar_title = '<a href="'.$url.'">'
|
||||
.$fields[$page['chronology_field']]['label'].'</a>';
|
||||
$calendar_title.= $calendar->get_display_name();
|
||||
$template->assign('chronology',
|
||||
array(
|
||||
'TITLE' => $calendar_title
|
||||
)
|
||||
);
|
||||
} // end category calling
|
||||
|
||||
if ($must_show_list)
|
||||
{
|
||||
if ( isset($page['super_order_by']) )
|
||||
{
|
||||
$order_by = $conf['order_by'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( count($page['chronology_date'])==0
|
||||
or in_array('any', $page['chronology_date']) )
|
||||
{// selected period is very big so we show newest first
|
||||
$order = ' DESC, ';
|
||||
}
|
||||
else
|
||||
{// selected period is small (month,week) so we show oldest first
|
||||
$order = ' ASC, ';
|
||||
}
|
||||
$order_by = str_replace(
|
||||
'ORDER BY ',
|
||||
'ORDER BY '.$calendar->date_field.$order, $conf['order_by']
|
||||
);
|
||||
}
|
||||
|
||||
if ('categories'==$page['section'] && !isset($page['category'])
|
||||
&& ( count($page['chronology_date'])==0
|
||||
OR ($page['chronology_date'][0]=='any' && count($page['chronology_date'])==1) )
|
||||
)
|
||||
{
|
||||
$cache_key = $persistent_cache->make_key($user['id'].$user['cache_update_time']
|
||||
.$calendar->date_field.$order_by);
|
||||
}
|
||||
|
||||
if ( !isset($cache_key) || !$persistent_cache->get($cache_key, $page['items']))
|
||||
{
|
||||
$query = 'SELECT DISTINCT id '
|
||||
.$calendar->inner_sql.'
|
||||
'.$calendar->get_date_where().'
|
||||
'.$order_by;
|
||||
$page['items'] = array_from_query($query, 'id');
|
||||
if ( isset($cache_key) )
|
||||
$persistent_cache->set($cache_key, $page['items']);
|
||||
}
|
||||
}
|
||||
pwg_debug('end initialize_calendar');
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,806 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\category
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Callback used for sorting by global_rank
|
||||
*/
|
||||
function global_rank_compare($a, $b)
|
||||
{
|
||||
return strnatcasecmp($a['global_rank'], $b['global_rank']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used for sorting by rank
|
||||
*/
|
||||
function rank_compare($a, $b)
|
||||
{
|
||||
return $a['rank'] - $b['rank'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the category accessible to the connected user ?
|
||||
* If the user is not authorized to see this category, script exits
|
||||
*
|
||||
* @param int $category_id
|
||||
*/
|
||||
function check_restrictions($category_id)
|
||||
{
|
||||
global $user;
|
||||
|
||||
// $filter['visible_categories'] and $filter['visible_images']
|
||||
// are not used because it's not necessary (filter <> restriction)
|
||||
if (in_array($category_id, explode(',', $user['forbidden_categories'])))
|
||||
{
|
||||
access_denied();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns template vars for main categories menu.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function get_categories_menu()
|
||||
{
|
||||
global $page, $user, $filter, $conf;
|
||||
|
||||
$query = '
|
||||
SELECT ';
|
||||
// From CATEGORIES_TABLE
|
||||
$query.= '
|
||||
id, name, permalink, nb_images, global_rank,';
|
||||
// From USER_CACHE_CATEGORIES_TABLE
|
||||
$query.= '
|
||||
date_last, max_date_last, count_images, count_categories';
|
||||
|
||||
// $user['forbidden_categories'] including with USER_CACHE_CATEGORIES_TABLE
|
||||
$query.= '
|
||||
FROM '.CATEGORIES_TABLE.' INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.'
|
||||
ON id = cat_id and user_id = '.$user['id'];
|
||||
|
||||
// Always expand when filter is activated
|
||||
if (!$user['expand'] and !$filter['enabled'])
|
||||
{
|
||||
$where = '
|
||||
(id_uppercat is NULL';
|
||||
if (isset($page['category']))
|
||||
{
|
||||
$where .= ' OR id_uppercat IN ('.$page['category']['uppercats'].')';
|
||||
}
|
||||
$where .= ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$where = '
|
||||
'.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'visible_categories' => 'id',
|
||||
),
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$where = trigger_change('get_categories_menu_sql_where',
|
||||
$where, $user['expand'], $filter['enabled'] );
|
||||
|
||||
$query.= '
|
||||
WHERE '.$where.'
|
||||
;';
|
||||
|
||||
$result = pwg_query($query);
|
||||
$cats = array();
|
||||
$selected_category = isset($page['category']) ? $page['category'] : null;
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$child_date_last = @$row['max_date_last']> @$row['date_last'];
|
||||
$row = array_merge($row,
|
||||
array(
|
||||
'NAME' => trigger_change(
|
||||
'render_category_name',
|
||||
$row['name'],
|
||||
'get_categories_menu'
|
||||
),
|
||||
'TITLE' => get_display_images_count(
|
||||
$row['nb_images'],
|
||||
$row['count_images'],
|
||||
$row['count_categories'],
|
||||
false,
|
||||
' / '
|
||||
),
|
||||
'URL' => make_index_url(array('category' => $row)),
|
||||
'LEVEL' => substr_count($row['global_rank'], '.') + 1,
|
||||
'SELECTED' => ($selected_category!==null && $selected_category['id'] == $row['id']) ? true : false,
|
||||
'IS_UPPERCAT' => ($selected_category!==null && $selected_category['id_uppercat'] == $row['id']) ? true : false,
|
||||
)
|
||||
);
|
||||
if ($conf['index_new_icon'])
|
||||
{
|
||||
$row['icon_ts'] = get_icon($row['max_date_last'], $child_date_last);
|
||||
}
|
||||
$cats[] = $row;
|
||||
if ($row['id']==@$page['category']['id']) //save the number of subcats for later optim
|
||||
$page['category']['count_categories'] = $row['count_categories'];
|
||||
}
|
||||
usort($cats, 'global_rank_compare');
|
||||
|
||||
// Update filtered data
|
||||
if (function_exists('update_cats_with_filtered_data'))
|
||||
{
|
||||
update_cats_with_filtered_data($cats);
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves informations about a category.
|
||||
*
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
function get_cat_info($id)
|
||||
{
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE id = '.$id.'
|
||||
;';
|
||||
$cat = pwg_db_fetch_assoc(pwg_query($query));
|
||||
if (empty($cat))
|
||||
return null;
|
||||
|
||||
foreach ($cat as $k => $v)
|
||||
{
|
||||
// If the field is true or false, the variable is transformed into a
|
||||
// boolean value.
|
||||
if ($cat[$k] == 'true' or $cat[$k] == 'false')
|
||||
{
|
||||
$cat[$k] = get_boolean( $cat[$k] );
|
||||
}
|
||||
}
|
||||
|
||||
$upper_ids = explode(',', $cat['uppercats']);
|
||||
if ( count($upper_ids)==1 )
|
||||
{// no need to make a query for level 1
|
||||
$cat['upper_names'] = array(
|
||||
array(
|
||||
'id' => $cat['id'],
|
||||
'name' => $cat['name'],
|
||||
'permalink' => $cat['permalink'],
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$query = '
|
||||
SELECT id, name, permalink
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE id IN ('.$cat['uppercats'].')
|
||||
;';
|
||||
$names = query2array($query, 'id');
|
||||
|
||||
// category names must be in the same order than uppercats list
|
||||
$cat['upper_names'] = array();
|
||||
foreach ($upper_ids as $cat_id)
|
||||
{
|
||||
$cat['upper_names'][] = $names[$cat_id];
|
||||
}
|
||||
}
|
||||
return $cat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of image orders available for users/visitors.
|
||||
* Each entry is an array containing
|
||||
* 0: name
|
||||
* 1: SQL ORDER command
|
||||
* 2: visiblity (true or false)
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function get_category_preferred_image_orders()
|
||||
{
|
||||
global $conf, $page;
|
||||
|
||||
return trigger_change('get_category_preferred_image_orders', array(
|
||||
array(l10n('Default'), '', true),
|
||||
array(l10n('Photo title, A → Z'), 'name ASC', true),
|
||||
array(l10n('Photo title, Z → A'), 'name DESC', true),
|
||||
array(l10n('Date created, new → old'), 'date_creation DESC', true),
|
||||
array(l10n('Date created, old → new'), 'date_creation ASC', true),
|
||||
array(l10n('Date posted, new → old'), 'date_available DESC', true),
|
||||
array(l10n('Date posted, old → new'), 'date_available ASC', true),
|
||||
array(l10n('Rating score, high → low'), 'rating_score DESC', $conf['rate']),
|
||||
array(l10n('Rating score, low → high'), 'rating_score ASC', $conf['rate']),
|
||||
array(l10n('Visits, high → low'), 'hit DESC', true),
|
||||
array(l10n('Visits, low → high'), 'hit ASC', true),
|
||||
array(l10n('Permissions'), 'level DESC', is_admin()),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a template var useable with {html_options} from a list of categories
|
||||
*
|
||||
* @param array[] $categories (at least id,name,global_rank,uppercats for each)
|
||||
* @param int[] $selected ids of selected items
|
||||
* @param string $blockname variable name in template
|
||||
* @param bool $fullname full breadcrumb or not
|
||||
*/
|
||||
function display_select_categories($categories,
|
||||
$selecteds,
|
||||
$blockname,
|
||||
$fullname = true)
|
||||
{
|
||||
global $template;
|
||||
|
||||
$tpl_cats = array();
|
||||
foreach ($categories as $category)
|
||||
{
|
||||
if ($fullname)
|
||||
{
|
||||
$option = strip_tags(
|
||||
get_cat_display_name_cache(
|
||||
$category['uppercats'],
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$option = str_repeat(' ',
|
||||
(3 * substr_count($category['global_rank'], '.')));
|
||||
$option.= '- ';
|
||||
$option.= strip_tags(
|
||||
trigger_change(
|
||||
'render_category_name',
|
||||
$category['name'],
|
||||
'display_select_categories'
|
||||
)
|
||||
);
|
||||
}
|
||||
$tpl_cats[ $category['id'] ] = $option;
|
||||
}
|
||||
|
||||
$template->assign( $blockname, $tpl_cats);
|
||||
$template->assign( $blockname.'_selected', $selecteds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as display_select_categories but categories are ordered by rank
|
||||
* @see display_select_categories()
|
||||
*/
|
||||
function display_select_cat_wrapper($query,
|
||||
$selecteds,
|
||||
$blockname,
|
||||
$fullname = true)
|
||||
{
|
||||
$categories = query2array($query);
|
||||
usort($categories, 'global_rank_compare');
|
||||
display_select_categories($categories, $selecteds, $blockname, $fullname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all subcategory identifiers of given category ids
|
||||
*
|
||||
* @param int[] $ids
|
||||
* @return int[]
|
||||
*/
|
||||
function get_subcat_ids($ids)
|
||||
{
|
||||
$query = '
|
||||
SELECT DISTINCT(id)
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE ';
|
||||
foreach ($ids as $num => $category_id)
|
||||
{
|
||||
is_numeric($category_id)
|
||||
or trigger_error(
|
||||
'get_subcat_ids expecting numeric, not '.gettype($category_id),
|
||||
E_USER_WARNING
|
||||
);
|
||||
if ($num > 0)
|
||||
{
|
||||
$query.= '
|
||||
OR ';
|
||||
}
|
||||
$query.= 'uppercats '.DB_REGEX_OPERATOR.' \'(^|,)'.$category_id.'(,|$)\'';
|
||||
}
|
||||
$query.= '
|
||||
;';
|
||||
return query2array($query, null, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a matching category id from a potential list of permalinks
|
||||
*
|
||||
* @param string[] $permalinks
|
||||
* @param int &$idx filled with the index in $permalinks that matches
|
||||
* @return int|null
|
||||
*/
|
||||
function get_cat_id_from_permalinks($permalinks, &$idx)
|
||||
{
|
||||
$in = '';
|
||||
foreach($permalinks as $permalink)
|
||||
{
|
||||
if ( !empty($in) ) $in.=', ';
|
||||
$in .= '\''.$permalink.'\'';
|
||||
}
|
||||
$query ='
|
||||
SELECT cat_id AS id, permalink, 1 AS is_old
|
||||
FROM '.OLD_PERMALINKS_TABLE.'
|
||||
WHERE permalink IN ('.$in.')
|
||||
UNION
|
||||
SELECT id, permalink, 0 AS is_old
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE permalink IN ('.$in.')
|
||||
;';
|
||||
$perma_hash = query2array($query, 'permalink');
|
||||
|
||||
if ( empty($perma_hash) )
|
||||
return null;
|
||||
for ($i=count($permalinks)-1; $i>=0; $i--)
|
||||
{
|
||||
if ( isset( $perma_hash[ $permalinks[$i] ] ) )
|
||||
{
|
||||
$idx = $i;
|
||||
$cat_id = $perma_hash[ $permalinks[$i] ]['id'];
|
||||
if ($perma_hash[ $permalinks[$i] ]['is_old'])
|
||||
{
|
||||
$query='
|
||||
UPDATE '.OLD_PERMALINKS_TABLE.' SET last_hit=NOW(), hit=hit+1
|
||||
WHERE permalink=\''.$permalinks[$i].'\' AND cat_id='.$cat_id.'
|
||||
LIMIT 1';
|
||||
pwg_query($query);
|
||||
}
|
||||
return $cat_id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display text for images counter of category
|
||||
*
|
||||
* @param int $cat_nb_images nb images directly in category
|
||||
* @param int $cat_count_images nb images in category (including subcats)
|
||||
* @param int $cat_count_categories nb subcats
|
||||
* @param bool $short_message if true append " in this album"
|
||||
* @param string $separator
|
||||
* @return string
|
||||
*/
|
||||
function get_display_images_count($cat_nb_images, $cat_count_images, $cat_count_categories, $short_message = true, $separator = '\n')
|
||||
{
|
||||
$display_text = '';
|
||||
|
||||
if ($cat_count_images > 0)
|
||||
{
|
||||
if ($cat_nb_images > 0 and $cat_nb_images < $cat_count_images)
|
||||
{
|
||||
$display_text.= get_display_images_count($cat_nb_images, $cat_nb_images, 0, $short_message, $separator).$separator;
|
||||
$cat_count_images-= $cat_nb_images;
|
||||
$cat_nb_images = 0;
|
||||
}
|
||||
|
||||
//at least one image direct or indirect
|
||||
$display_text.= l10n_dec('%d photo', '%d photos', $cat_count_images);
|
||||
|
||||
if ($cat_count_categories == 0 or $cat_nb_images == $cat_count_images)
|
||||
{
|
||||
//no descendant categories or descendants do not contain images
|
||||
if (!$short_message)
|
||||
{
|
||||
$display_text.= ' '.l10n('in this album');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$display_text.= ' '.l10n_dec('in %d sub-album', 'in %d sub-albums', $cat_count_categories);
|
||||
}
|
||||
}
|
||||
|
||||
return $display_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a random photo among all photos inside an album (including sub-albums)
|
||||
*
|
||||
* @param array $category (at least id,uppercats,count_images)
|
||||
* @param bool $recursive
|
||||
* @return int|null
|
||||
*/
|
||||
function get_random_image_in_category($category, $recursive=true)
|
||||
{
|
||||
$image_id = null;
|
||||
if ($category['count_images']>0)
|
||||
{
|
||||
$query = '
|
||||
SELECT image_id
|
||||
FROM '.CATEGORIES_TABLE.' AS c
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.category_id = c.id
|
||||
WHERE ';
|
||||
if ($recursive)
|
||||
{
|
||||
$query.= '
|
||||
(c.id='.$category['id'].' OR uppercats LIKE \''.$category['uppercats'].',%\')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query.= '
|
||||
c.id='.$category['id'];
|
||||
}
|
||||
$query.= '
|
||||
'.get_sql_condition_FandF
|
||||
(
|
||||
array
|
||||
(
|
||||
'forbidden_categories' => 'c.id',
|
||||
'visible_categories' => 'c.id',
|
||||
'visible_images' => 'image_id',
|
||||
),
|
||||
"\n AND"
|
||||
).'
|
||||
ORDER BY '.DB_RANDOM_FUNCTION.'()
|
||||
LIMIT 1
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
if (pwg_db_num_rows($result) > 0)
|
||||
{
|
||||
list($image_id) = pwg_db_fetch_row($result);
|
||||
}
|
||||
}
|
||||
|
||||
return $image_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get computed array of categories, that means cache data of all categories
|
||||
* available for the current user (count_categories, count_images, etc.).
|
||||
*
|
||||
* @param array &$userdata
|
||||
* @param int $filter_days number of recent days to filter on or null
|
||||
* @return array
|
||||
*/
|
||||
function get_computed_categories(&$userdata, $filter_days=null)
|
||||
{
|
||||
$query = 'SELECT c.id AS cat_id, id_uppercat';
|
||||
$query.= ', global_rank';
|
||||
// Count by date_available to avoid count null
|
||||
$query .= ',
|
||||
MAX(date_available) AS date_last, COUNT(date_available) AS nb_images
|
||||
FROM '.CATEGORIES_TABLE.' as c
|
||||
LEFT JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.category_id = c.id
|
||||
LEFT JOIN '.IMAGES_TABLE.' AS i
|
||||
ON ic.image_id = i.id
|
||||
AND i.level<='.$userdata['level'];
|
||||
|
||||
if ( isset($filter_days) )
|
||||
{
|
||||
$query .= ' AND i.date_available > '.pwg_db_get_recent_period_expression($filter_days);
|
||||
}
|
||||
|
||||
if ( !empty($userdata['forbidden_categories']) )
|
||||
{
|
||||
$query.= '
|
||||
WHERE c.id NOT IN ('.$userdata['forbidden_categories'].')';
|
||||
}
|
||||
|
||||
$query.= '
|
||||
GROUP BY c.id';
|
||||
|
||||
$result = pwg_query($query);
|
||||
|
||||
$userdata['last_photo_date'] = null;
|
||||
$cats = array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$row['user_id'] = $userdata['id'];
|
||||
$row['nb_categories'] = 0;
|
||||
$row['count_categories'] = 0;
|
||||
$row['count_images'] = (int)$row['nb_images'];
|
||||
$row['max_date_last'] = $row['date_last'];
|
||||
if ($row['date_last'] > $userdata['last_photo_date'])
|
||||
{
|
||||
$userdata['last_photo_date'] = $row['date_last'];
|
||||
}
|
||||
|
||||
$cats[$row['cat_id']] = $row;
|
||||
}
|
||||
|
||||
// it is important to logically sort the albums because some operations
|
||||
// (like removal) rely on this logical order. Child album doesn't always
|
||||
// have a bigger id than its parent (if it was moved afterwards).
|
||||
uasort($cats, 'global_rank_compare');
|
||||
|
||||
foreach ($cats as $cat)
|
||||
{
|
||||
if ( !isset( $cat['id_uppercat'] ) )
|
||||
continue;
|
||||
|
||||
// Piwigo before 2.5.3 may have generated inconsistent permissions, ie
|
||||
// private album A1/A2 permitted to user U1 but private album A1 not
|
||||
// permitted to U1.
|
||||
//
|
||||
// TODO 2.7: add an upgrade script to repair permissions and remove this
|
||||
// test
|
||||
if ( !isset($cats[ $cat['id_uppercat'] ]))
|
||||
continue;
|
||||
|
||||
$parent = & $cats[ $cat['id_uppercat'] ];
|
||||
$parent['nb_categories']++;
|
||||
|
||||
do
|
||||
{
|
||||
$parent['count_images'] += $cat['nb_images'];
|
||||
$parent['count_categories']++;
|
||||
|
||||
if ((empty($parent['max_date_last'])) or ($parent['max_date_last'] < $cat['date_last']))
|
||||
{
|
||||
$parent['max_date_last'] = $cat['date_last'];
|
||||
}
|
||||
|
||||
if ( !isset( $parent['id_uppercat'] ) )
|
||||
break;
|
||||
$parent = & $cats[$parent['id_uppercat']];
|
||||
}
|
||||
while (true);
|
||||
unset($parent);
|
||||
}
|
||||
|
||||
if ( isset($filter_days) )
|
||||
{
|
||||
foreach ($cats as $category)
|
||||
{
|
||||
if (empty($category['max_date_last']))
|
||||
{
|
||||
remove_computed_category($cats, $category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a category from computed array of categories and updates counters.
|
||||
*
|
||||
* @param array &$cats
|
||||
* @param array $cat category to remove
|
||||
*/
|
||||
function remove_computed_category(&$cats, $cat)
|
||||
{
|
||||
if ( isset($cats[$cat['id_uppercat']]) )
|
||||
{
|
||||
$parent = &$cats[ $cat['id_uppercat'] ];
|
||||
$parent['nb_categories']--;
|
||||
|
||||
do
|
||||
{
|
||||
$parent['count_images'] -= $cat['nb_images'];
|
||||
$parent['count_categories'] -= 1+$cat['count_categories'];
|
||||
|
||||
if ( !isset($cats[$parent['id_uppercat']]) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
$parent = &$cats[$parent['id_uppercat']];
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
unset($cats[$cat['cat_id']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of image ids corresponding to given categories.
|
||||
* AND & OR mode supported.
|
||||
*
|
||||
* @param int[] $cat_ids
|
||||
* @param string mode
|
||||
* @param string $extra_images_where_sql - optionally apply a sql where filter to retrieved images
|
||||
* @param string $order_by - optionally overwrite default photo order
|
||||
* @param bool $user_permissions
|
||||
* @return array
|
||||
*/
|
||||
function get_image_ids_for_categories($cat_ids, $mode='AND', $extra_images_where_sql='', $order_by='', $use_permissions=true)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (empty($cat_ids))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT id
|
||||
FROM '.IMAGES_TABLE.' i
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' ic ON id=ic.image_id
|
||||
WHERE category_id IN ('.implode(',', $cat_ids).')';
|
||||
|
||||
if ($use_permissions)
|
||||
{
|
||||
$query.= get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
"\n AND"
|
||||
);
|
||||
}
|
||||
|
||||
$query.= (empty($extra_images_where_sql) ? '' : " \nAND (".$extra_images_where_sql.')').'
|
||||
GROUP BY id';
|
||||
|
||||
if ($mode=='AND' and count($cat_ids)>1)
|
||||
{
|
||||
$query .= '
|
||||
HAVING COUNT(DISTINCT category_id)='.count($cat_ids);
|
||||
}
|
||||
$query .= "\n".(empty($order_by) ? $conf['order_by'] : $order_by);
|
||||
|
||||
return query2array($query, null, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of categories corresponding to given items.
|
||||
*
|
||||
* @param int[] $items
|
||||
* @param int $max
|
||||
* @param int[] $excluded_cat_ids
|
||||
* @return array [id, name, counter, url_name]
|
||||
*/
|
||||
function get_common_categories($items, $max=null, $excluded_cat_ids=array(), $use_permissions=true)
|
||||
{
|
||||
if (empty($items))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
c.id,
|
||||
c.uppercats,
|
||||
count(*) AS counter
|
||||
FROM '.IMAGE_CATEGORY_TABLE.'
|
||||
INNER JOIN '.CATEGORIES_TABLE.' c ON category_id = id
|
||||
WHERE image_id IN ('.implode(',', $items).')';
|
||||
|
||||
if ($use_permissions)
|
||||
{
|
||||
$query.= get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
),
|
||||
"\n AND"
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($excluded_cat_ids))
|
||||
{
|
||||
$query.='
|
||||
AND category_id NOT IN ('.implode(',', $excluded_cat_ids).')';
|
||||
}
|
||||
|
||||
$query .='
|
||||
GROUP BY c.id
|
||||
ORDER BY ';
|
||||
if (isset($max))
|
||||
{
|
||||
$query .= 'counter DESC
|
||||
LIMIT '.$max;
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
|
||||
$result = pwg_query($query);
|
||||
$cats = array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$cats[ $row['id'] ] = $row;
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
function get_related_categories_menu($items, $excluded_cat_ids=array())
|
||||
{
|
||||
global $page, $conf;
|
||||
|
||||
$common_cats = get_common_categories($items, $conf['related_albums_display_limit'], $excluded_cat_ids);
|
||||
// echo '<pre>'; print_r($common_cats); echo '</pre>';
|
||||
|
||||
if (count($common_cats) == 0)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$cat_ids = array();
|
||||
// now we add the upper categories and useful values such as depth level and url
|
||||
foreach ($common_cats as $cat)
|
||||
{
|
||||
foreach (explode(',', $cat['uppercats']) as $uppercat)
|
||||
{
|
||||
@$cat_ids[$uppercat]++;
|
||||
}
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
permalink,
|
||||
id_uppercat,
|
||||
uppercats,
|
||||
global_rank
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE id IN ('.implode(',', array_keys($cat_ids)).')
|
||||
;';
|
||||
$cats = query2array($query);
|
||||
usort($cats, 'global_rank_compare');
|
||||
|
||||
$index_of_cat = array();
|
||||
|
||||
foreach ($cats as $idx => $cat)
|
||||
{
|
||||
$index_of_cat[ $cat['id'] ] = $idx;
|
||||
$cats[$idx]['LEVEL'] = substr_count($cat['global_rank'], '.') + 1;
|
||||
$cats[$idx]['name'] = trigger_change('render_category_name', $cat['name'], $cat);
|
||||
|
||||
// if the category is directly linked to the items, we add an URL + counter
|
||||
if (isset($common_cats[ $cat['id'] ]))
|
||||
{
|
||||
$cats[$idx]['count_images'] = $common_cats[ $cat['id'] ]['counter'];
|
||||
|
||||
$url_params = array();
|
||||
if (isset($page['category']))
|
||||
{
|
||||
$url_params['category'] = $page['category'];
|
||||
|
||||
$url_params['combined_categories'] = array($cat);
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$url_params['combined_categories'] = array_merge($page['combined_categories'], array($cat));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$url_params['category'] = $cat;
|
||||
}
|
||||
|
||||
$cats[$idx]['url'] = make_index_url($url_params);
|
||||
}
|
||||
|
||||
// let's find how many sub-categories we have for each category. 3 options:
|
||||
// 1. direct sub-albums
|
||||
// 2. total indirect sub-albums
|
||||
// 3. number of sub-albums containing photos
|
||||
//
|
||||
// Option 3 seems more appropriate here.
|
||||
if (!empty($cat['id_uppercat']) and @$cats[$idx]['count_images'] > 0)
|
||||
{
|
||||
foreach (array_slice(explode(',', $cat['uppercats']), 0, -1) as $uppercat_id)
|
||||
{
|
||||
@$cats[ $index_of_cat[ $uppercat_id ] ]['count_categories']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\comment
|
||||
*/
|
||||
|
||||
|
||||
add_event_handler('user_comment_check', 'user_comment_check');
|
||||
|
||||
/**
|
||||
* Does basic check on comment and returns action to perform.
|
||||
* This method is called by a trigger_change()
|
||||
*
|
||||
* @param string $action before check
|
||||
* @param array $comment
|
||||
* @return string validate, moderate, reject
|
||||
*/
|
||||
function user_comment_check($action, $comment)
|
||||
{
|
||||
global $conf,$user;
|
||||
|
||||
if ($action=='reject')
|
||||
return $action;
|
||||
|
||||
$my_action = $conf['comment_spam_reject'] ? 'reject':'moderate';
|
||||
|
||||
if ($action==$my_action)
|
||||
return $action;
|
||||
|
||||
// we do here only BASIC spam check (plugins can do more)
|
||||
if ( !is_a_guest() )
|
||||
return $action;
|
||||
|
||||
$link_count = preg_match_all( '/https?:\/\//',
|
||||
$comment['content'], $matches);
|
||||
|
||||
if ( strpos($comment['author'], 'http://')!==false )
|
||||
{
|
||||
$link_count++;
|
||||
}
|
||||
|
||||
if ( $link_count>$conf['comment_spam_max_links'] )
|
||||
{
|
||||
$_POST['cr'][] = 'links';
|
||||
return $my_action;
|
||||
}
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to insert a user comment and returns action to perform.
|
||||
*
|
||||
* @param array &$comm
|
||||
* @param string $key secret key sent back to the browser
|
||||
* @param array &$infos output array of error messages
|
||||
* @return string validate, moderate, reject
|
||||
*/
|
||||
function insert_user_comment(&$comm, $key, &$infos)
|
||||
{
|
||||
global $conf, $user;
|
||||
|
||||
$comm = array_merge( $comm,
|
||||
array(
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
'agent' => $_SERVER['HTTP_USER_AGENT']
|
||||
)
|
||||
);
|
||||
|
||||
$infos = array();
|
||||
if (!$conf['comments_validation'] or is_admin())
|
||||
{
|
||||
$comment_action='validate'; //one of validate, moderate, reject
|
||||
}
|
||||
else
|
||||
{
|
||||
$comment_action='moderate'; //one of validate, moderate, reject
|
||||
}
|
||||
|
||||
// display author field if the user status is guest or generic
|
||||
if (!is_classic_user())
|
||||
{
|
||||
if ( empty($comm['author']) )
|
||||
{
|
||||
if ($conf['comments_author_mandatory'])
|
||||
{
|
||||
$infos[] = l10n('Username is mandatory');
|
||||
$comment_action='reject';
|
||||
}
|
||||
$comm['author'] = 'guest';
|
||||
}
|
||||
$comm['author_id'] = $conf['guest_id'];
|
||||
// if a guest try to use the name of an already existing user, he must be
|
||||
// rejected
|
||||
if ( $comm['author'] != 'guest' )
|
||||
{
|
||||
$query = '
|
||||
SELECT COUNT(*) AS user_exists
|
||||
FROM '.USERS_TABLE.'
|
||||
WHERE '.$conf['user_fields']['username']." = '".addslashes($comm['author'])."'";
|
||||
$row = pwg_db_fetch_assoc( pwg_query( $query ) );
|
||||
if ( $row['user_exists'] == 1 )
|
||||
{
|
||||
$infos[] = l10n('This login is already used by another user');
|
||||
$comment_action='reject';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$comm['author'] = addslashes($user['username']);
|
||||
$comm['author_id'] = $user['id'];
|
||||
}
|
||||
|
||||
if ( empty($comm['content']) )
|
||||
{ // empty comment content
|
||||
$comment_action='reject';
|
||||
}
|
||||
|
||||
if ( !verify_ephemeral_key(@$key, $comm['image_id']) )
|
||||
{
|
||||
$comment_action='reject';
|
||||
$_POST['cr'][] = 'key'; // rvelices: I use this outside to see how spam robots work
|
||||
}
|
||||
|
||||
// website
|
||||
if (!empty($comm['website_url']))
|
||||
{
|
||||
if (!$conf['comments_enable_website'])
|
||||
{ // honeypot: if the field is disabled, it should be empty !
|
||||
$comment_action='reject';
|
||||
$_POST['cr'][] = 'website_url';
|
||||
}
|
||||
else
|
||||
{
|
||||
$comm['website_url'] = strip_tags($comm['website_url']);
|
||||
if (!preg_match('/^https?/i', $comm['website_url']))
|
||||
{
|
||||
$comm['website_url'] = 'http://'.$comm['website_url'];
|
||||
}
|
||||
if (!url_check_format($comm['website_url']))
|
||||
{
|
||||
$infos[] = l10n('Your website URL is invalid');
|
||||
$comment_action='reject';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// email
|
||||
if (empty($comm['email']))
|
||||
{
|
||||
if (!empty($user['email']))
|
||||
{
|
||||
$comm['email'] = $user['email'];
|
||||
}
|
||||
elseif ($conf['comments_email_mandatory'])
|
||||
{
|
||||
$infos[] = l10n('Email address is missing. Please specify an email address.');
|
||||
$comment_action='reject';
|
||||
}
|
||||
}
|
||||
elseif (!email_check_format($comm['email']))
|
||||
{
|
||||
$infos[] = l10n('mail address must be like xxx@yyy.eee (example : jack@altern.org)');
|
||||
$comment_action='reject';
|
||||
}
|
||||
|
||||
// anonymous id = ip address
|
||||
$ip_components = explode('.', $comm['ip']);
|
||||
if (count($ip_components) > 3)
|
||||
{
|
||||
array_pop($ip_components);
|
||||
}
|
||||
$anonymous_id = implode('.', $ip_components);
|
||||
|
||||
if ($comment_action!='reject' and $conf['anti-flood_time']>0 and !is_admin())
|
||||
{ // anti-flood system
|
||||
$reference_date = pwg_db_get_flood_period_expression($conf['anti-flood_time']);
|
||||
|
||||
$query = '
|
||||
SELECT count(1) FROM '.COMMENTS_TABLE.'
|
||||
WHERE date > '.$reference_date.'
|
||||
AND author_id = '.$comm['author_id'];
|
||||
if (!is_classic_user())
|
||||
{
|
||||
$query.= '
|
||||
AND anonymous_id LIKE "'.$anonymous_id.'.%"';
|
||||
}
|
||||
$query.= '
|
||||
;';
|
||||
|
||||
list($counter) = pwg_db_fetch_row(pwg_query($query));
|
||||
if ( $counter > 0 )
|
||||
{
|
||||
$infos[] = l10n('Anti-flood system : please wait for a moment before trying to post another comment');
|
||||
$comment_action='reject';
|
||||
$_POST['cr'][] = 'flood_time';
|
||||
}
|
||||
}
|
||||
|
||||
// perform more spam check
|
||||
$comment_action = trigger_change('user_comment_check',
|
||||
$comment_action, $comm
|
||||
);
|
||||
|
||||
if ( $comment_action!='reject' )
|
||||
{
|
||||
$query = '
|
||||
INSERT INTO '.COMMENTS_TABLE.'
|
||||
(author, author_id, anonymous_id, content, date, validated, validation_date, image_id, website_url, email)
|
||||
VALUES (
|
||||
\''.$comm['author'].'\',
|
||||
'.$comm['author_id'].',
|
||||
\''.$comm['ip'].'\',
|
||||
\''.$comm['content'].'\',
|
||||
NOW(),
|
||||
\''.($comment_action=='validate' ? 'true':'false').'\',
|
||||
'.($comment_action=='validate' ? 'NOW()':'NULL').',
|
||||
'.$comm['image_id'].',
|
||||
'.(!empty($comm['website_url']) ? '\''.$comm['website_url'].'\'' : 'NULL').',
|
||||
'.(!empty($comm['email']) ? '\''.$comm['email'].'\'' : 'NULL').'
|
||||
)
|
||||
';
|
||||
pwg_query($query);
|
||||
$comm['id'] = pwg_db_insert_id(COMMENTS_TABLE);
|
||||
|
||||
invalidate_user_cache_nb_comments();
|
||||
|
||||
if ( ($conf['email_admin_on_comment'] && 'validate' == $comment_action)
|
||||
or ($conf['email_admin_on_comment_validation'] and 'moderate' == $comment_action))
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
|
||||
|
||||
$comment_url = get_absolute_root_url().'comments.php?comment_id='.$comm['id'];
|
||||
|
||||
$keyargs_content = array(
|
||||
get_l10n_args('Author: %s', stripslashes($comm['author']) ),
|
||||
get_l10n_args('Email: %s', stripslashes($comm['email']) ),
|
||||
get_l10n_args('Comment: %s', stripslashes($comm['content']) ),
|
||||
get_l10n_args(''),
|
||||
get_l10n_args('Manage this user comment: %s', $comment_url),
|
||||
);
|
||||
|
||||
if ('moderate' == $comment_action)
|
||||
{
|
||||
$keyargs_content[] = get_l10n_args('(!) This comment requires validation');
|
||||
}
|
||||
|
||||
pwg_mail_notification_admins(
|
||||
get_l10n_args('Comment by %s', stripslashes($comm['author']) ),
|
||||
$keyargs_content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $comment_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to delete a (or more) user comment.
|
||||
* only admin can delete all comments
|
||||
* other users can delete their own comments
|
||||
*
|
||||
* @param int|int[] $comment_id
|
||||
* @return bool false if nothing deleted
|
||||
*/
|
||||
function delete_user_comment($comment_id)
|
||||
{
|
||||
$user_where_clause = '';
|
||||
if (!is_admin())
|
||||
{
|
||||
$user_where_clause = ' AND author_id = \''.$GLOBALS['user']['id'].'\'';
|
||||
}
|
||||
|
||||
if (is_array($comment_id))
|
||||
$where_clause = 'id IN('.implode(',', $comment_id).')';
|
||||
else
|
||||
$where_clause = 'id = '.$comment_id;
|
||||
|
||||
$query = '
|
||||
DELETE FROM '.COMMENTS_TABLE.'
|
||||
WHERE '.$where_clause.
|
||||
$user_where_clause.'
|
||||
;';
|
||||
|
||||
if ( pwg_db_changes(pwg_query($query)) )
|
||||
{
|
||||
invalidate_user_cache_nb_comments();
|
||||
|
||||
email_admin('delete',
|
||||
array('author' => $GLOBALS['user']['username'],
|
||||
'comment_id' => $comment_id
|
||||
));
|
||||
trigger_notify('user_comment_deletion', $comment_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to update a user comment
|
||||
* only admin can update all comments
|
||||
* users can edit their own comments if admin allow them
|
||||
*
|
||||
* @param array $comment
|
||||
* @param string $post_key secret key sent back to the browser
|
||||
* @return string validate, moderate, reject
|
||||
*/
|
||||
|
||||
function update_user_comment($comment, $post_key)
|
||||
{
|
||||
global $conf, $page;
|
||||
|
||||
$comment_action = 'validate';
|
||||
|
||||
if ( !verify_ephemeral_key($post_key, $comment['image_id']) )
|
||||
{
|
||||
$comment_action='reject';
|
||||
}
|
||||
elseif (!$conf['comments_validation'] or is_admin()) // should the updated comment must be validated
|
||||
{
|
||||
$comment_action='validate'; //one of validate, moderate, reject
|
||||
}
|
||||
else
|
||||
{
|
||||
$comment_action='moderate'; //one of validate, moderate, reject
|
||||
}
|
||||
|
||||
// perform more spam check
|
||||
$comment_action =
|
||||
trigger_change('user_comment_check',
|
||||
$comment_action,
|
||||
array_merge($comment,
|
||||
array('author' => $GLOBALS['user']['username'])
|
||||
)
|
||||
);
|
||||
|
||||
// website
|
||||
if (!empty($comment['website_url']))
|
||||
{
|
||||
$comm['website_url'] = strip_tags($comm['website_url']);
|
||||
if (!preg_match('/^https?/i', $comment['website_url']))
|
||||
{
|
||||
$comment['website_url'] = 'http://'.$comment['website_url'];
|
||||
}
|
||||
if (!url_check_format($comment['website_url']))
|
||||
{
|
||||
$page['errors'][] = l10n('Your website URL is invalid');
|
||||
$comment_action='reject';
|
||||
}
|
||||
}
|
||||
|
||||
if ( $comment_action!='reject' )
|
||||
{
|
||||
$user_where_clause = '';
|
||||
if (!is_admin())
|
||||
{
|
||||
$user_where_clause = ' AND author_id = \''.
|
||||
$GLOBALS['user']['id'].'\'';
|
||||
}
|
||||
|
||||
$query = '
|
||||
UPDATE '.COMMENTS_TABLE.'
|
||||
SET content = \''.$comment['content'].'\',
|
||||
website_url = '.(!empty($comment['website_url']) ? '\''.$comment['website_url'].'\'' : 'NULL').',
|
||||
validated = \''.($comment_action=='validate' ? 'true':'false').'\',
|
||||
validation_date = '.($comment_action=='validate' ? 'NOW()':'NULL').'
|
||||
WHERE id = '.$comment['comment_id'].
|
||||
$user_where_clause.'
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
|
||||
// mail admin and ask to validate the comment
|
||||
if ($result and $conf['email_admin_on_comment_validation'] and 'moderate' == $comment_action)
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
|
||||
|
||||
$comment_url = get_absolute_root_url().'comments.php?comment_id='.$comment['comment_id'];
|
||||
|
||||
$keyargs_content = array(
|
||||
get_l10n_args('Author: %s', stripslashes($GLOBALS['user']['username']) ),
|
||||
get_l10n_args('Comment: %s', stripslashes($comment['content']) ),
|
||||
get_l10n_args(''),
|
||||
get_l10n_args('Manage this user comment: %s', $comment_url),
|
||||
get_l10n_args('(!) This comment requires validation'),
|
||||
);
|
||||
|
||||
pwg_mail_notification_admins(
|
||||
get_l10n_args('Comment by %s', stripslashes($GLOBALS['user']['username']) ),
|
||||
$keyargs_content
|
||||
);
|
||||
}
|
||||
// just mail admin
|
||||
elseif ($result)
|
||||
{
|
||||
email_admin('edit', array('author' => $GLOBALS['user']['username'],
|
||||
'content' => stripslashes($comment['content'])) );
|
||||
}
|
||||
}
|
||||
|
||||
return $comment_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies admins about updated or deleted comment.
|
||||
* Only used when no validation is needed, otherwise pwg_mail_notification_admins() is used.
|
||||
*
|
||||
* @param string $action edit, delete
|
||||
* @param array $comment
|
||||
*/
|
||||
function email_admin($action, $comment)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!in_array($action, array('edit', 'delete'))
|
||||
or (($action=='edit') and !$conf['email_admin_on_comment_edition'])
|
||||
or (($action=='delete') and !$conf['email_admin_on_comment_deletion']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
|
||||
|
||||
$keyargs_content = array(
|
||||
get_l10n_args('Author: %s', $comment['author']),
|
||||
);
|
||||
|
||||
if ($action=='delete')
|
||||
{
|
||||
$keyargs_content[] = get_l10n_args('This author removed the comment with id %d', $comment['comment_id']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$keyargs_content[] = get_l10n_args('This author modified following comment:');
|
||||
$keyargs_content[] = get_l10n_args('Comment: %s', $comment['content']);
|
||||
}
|
||||
|
||||
pwg_mail_notification_admins(
|
||||
get_l10n_args('Comment by %s', $comment['author']),
|
||||
$keyargs_content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the author id of a comment
|
||||
*
|
||||
* @param int $comment_id
|
||||
* @param bool $die_on_error
|
||||
* @return int
|
||||
*/
|
||||
function get_comment_author_id($comment_id, $die_on_error=true)
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
author_id
|
||||
FROM '.COMMENTS_TABLE.'
|
||||
WHERE id = '.$comment_id.'
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
if (pwg_db_num_rows($result) == 0)
|
||||
{
|
||||
if ($die_on_error)
|
||||
{
|
||||
fatal_error('Unknown comment identifier');
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
list($author_id) = pwg_db_fetch_row($result);
|
||||
|
||||
return $author_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to validate a user comment.
|
||||
*
|
||||
* @param int|int[] $comment_id
|
||||
*/
|
||||
function validate_user_comment($comment_id)
|
||||
{
|
||||
if (is_array($comment_id))
|
||||
$where_clause = 'id IN('.implode(',', $comment_id).')';
|
||||
else
|
||||
$where_clause = 'id = '.$comment_id;
|
||||
|
||||
$query = '
|
||||
UPDATE '.COMMENTS_TABLE.'
|
||||
SET validated = \'true\'
|
||||
, validation_date = NOW()
|
||||
WHERE '.$where_clause.'
|
||||
;';
|
||||
pwg_query($query);
|
||||
|
||||
invalidate_user_cache_nb_comments();
|
||||
trigger_notify('user_comment_validation', $comment_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache of nb comments for all users
|
||||
*/
|
||||
function invalidate_user_cache_nb_comments()
|
||||
{
|
||||
global $user;
|
||||
|
||||
unset($user['nb_available_comments']);
|
||||
|
||||
$query = '
|
||||
UPDATE '.USER_CACHE_TABLE.'
|
||||
SET nb_available_comments = NULL
|
||||
;';
|
||||
pwg_query($query);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\cookie
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to use for the Piwigo cookie.
|
||||
* If Piwigo is installed on :
|
||||
* http://domain.org/meeting/gallery/
|
||||
* it will return : "/meeting/gallery"
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function cookie_path()
|
||||
{
|
||||
if ( isset($_SERVER['REDIRECT_SCRIPT_NAME']) and
|
||||
!empty($_SERVER['REDIRECT_SCRIPT_NAME']) )
|
||||
{
|
||||
$scr = $_SERVER['REDIRECT_SCRIPT_NAME'];
|
||||
}
|
||||
else if ( isset($_SERVER['REDIRECT_URL']) )
|
||||
{
|
||||
// mod_rewrite is activated for upper level directories. we must set the
|
||||
// cookie to the path shown in the browser otherwise it will be discarded.
|
||||
if
|
||||
(
|
||||
isset($_SERVER['PATH_INFO']) and !empty($_SERVER['PATH_INFO']) and
|
||||
($_SERVER['REDIRECT_URL'] !== $_SERVER['PATH_INFO']) and
|
||||
(substr($_SERVER['REDIRECT_URL'],-strlen($_SERVER['PATH_INFO']))
|
||||
== $_SERVER['PATH_INFO'])
|
||||
)
|
||||
{
|
||||
$scr = substr($_SERVER['REDIRECT_URL'], 0,
|
||||
strlen($_SERVER['REDIRECT_URL'])-strlen($_SERVER['PATH_INFO']));
|
||||
}
|
||||
else
|
||||
{
|
||||
$scr = $_SERVER['REDIRECT_URL'];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$scr = $_SERVER['SCRIPT_NAME'];
|
||||
}
|
||||
|
||||
$scr = substr($scr,0,strrpos( $scr,'/'));
|
||||
|
||||
// add a trailing '/' if needed
|
||||
if ((strlen($scr) == 0) or ($scr[strlen($scr)-1] !== '/'))
|
||||
{
|
||||
$scr .= '/';
|
||||
}
|
||||
|
||||
if ( substr(PHPWG_ROOT_PATH,0,3)=='../')
|
||||
{ // this is maybe a plugin inside pwg directory
|
||||
// TODO - what if it is an external script outside PWG ?
|
||||
$scr = $scr.PHPWG_ROOT_PATH;
|
||||
while (1)
|
||||
{
|
||||
$new = preg_replace('#[^/]+/\.\.(/|$)#', '', $scr);
|
||||
if ($new==$scr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
$scr=$new;
|
||||
}
|
||||
}
|
||||
return $scr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistently stores a variable in pwg cookie.
|
||||
* Set $value to null to delete the cookie.
|
||||
*
|
||||
* @param string $car
|
||||
* @param mixed $value
|
||||
* @param int|null $expire
|
||||
* @return bool
|
||||
*/
|
||||
function pwg_set_cookie_var($var, $value, $expire=null)
|
||||
{
|
||||
if ($value==null or $expire===0)
|
||||
{
|
||||
unset($_COOKIE['pwg_'.$var]);
|
||||
return setcookie('pwg_'.$var, false, 0, cookie_path());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$_COOKIE['pwg_'.$var] = $value;
|
||||
$expire = is_numeric($expire) ? $expire : strtotime('+10 years');
|
||||
return setcookie('pwg_'.$var, $value, $expire, cookie_path());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a persistent variable in pwg cookie
|
||||
* @see pwg_set_cookie_var
|
||||
*
|
||||
* @param string $var
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
function pwg_get_cookie_var($var, $default = null)
|
||||
{
|
||||
if (isset($_COOKIE['pwg_'.$var]))
|
||||
{
|
||||
return $_COOKIE['pwg_'.$var];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\filter
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Updates data of categories with filtered values
|
||||
*
|
||||
* @param array &$cats
|
||||
*/
|
||||
function update_cats_with_filtered_data(&$cats)
|
||||
{
|
||||
global $filter;
|
||||
|
||||
if ($filter['enabled'])
|
||||
{
|
||||
$upd_fields = array('date_last', 'max_date_last', 'count_images', 'count_categories', 'nb_images');
|
||||
|
||||
foreach ($cats as $cat_id => $category)
|
||||
{
|
||||
foreach ($upd_fields as $upd_field)
|
||||
{
|
||||
$cats[$cat_id][$upd_field] = $filter['categories'][$category['id']][$upd_field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,673 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\html
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Generates breadcrumb from categories list.
|
||||
* Categories string returned contains categories as given in the input
|
||||
* array $cat_informations. $cat_informations array must be an array
|
||||
* of array( id=>?, name=>?, permalink=>?). If url input parameter is null,
|
||||
* returns only the categories name without links.
|
||||
*
|
||||
* @param array $cat_informations
|
||||
* @param string|null $url
|
||||
* @return string
|
||||
*/
|
||||
function get_cat_display_name($cat_informations, $url='')
|
||||
{
|
||||
global $conf;
|
||||
|
||||
//$output = '<a href="'.get_absolute_root_url().$conf['home_page'].'">'.l10n('Home').'</a>';
|
||||
$output = '';
|
||||
$is_first=true;
|
||||
|
||||
foreach ($cat_informations as $cat)
|
||||
{
|
||||
is_array($cat) or trigger_error(
|
||||
'get_cat_display_name wrong type for category ', E_USER_WARNING
|
||||
);
|
||||
|
||||
$cat['name'] = trigger_change(
|
||||
'render_category_name',
|
||||
$cat['name'],
|
||||
'get_cat_display_name'
|
||||
);
|
||||
|
||||
if ($is_first)
|
||||
{
|
||||
$is_first=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$output.= $conf['level_separator'];
|
||||
}
|
||||
|
||||
if ( !isset($url) )
|
||||
{
|
||||
$output.= $cat['name'];
|
||||
}
|
||||
elseif ($url == '')
|
||||
{
|
||||
$output.= '<a href="'
|
||||
.make_index_url(
|
||||
array(
|
||||
'category' => $cat,
|
||||
)
|
||||
)
|
||||
.'">';
|
||||
$output.= $cat['name'].'</a>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$output.= '<a href="'.PHPWG_ROOT_PATH.$url.$cat['id'].'">';
|
||||
$output.= $cat['name'].'</a>';
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates breadcrumb from categories list using a cache.
|
||||
* @see get_cat_display_name()
|
||||
*
|
||||
* @param string $uppercats
|
||||
* @param string|null $url
|
||||
* @param bool $single_link
|
||||
* @param string|null $link_class
|
||||
* @return string
|
||||
*/
|
||||
function get_cat_display_name_cache($uppercats,
|
||||
$url = '',
|
||||
$single_link = false,
|
||||
$link_class = null,
|
||||
$auth_key=null)
|
||||
{
|
||||
global $cache, $conf;
|
||||
|
||||
$add_url_params = array();
|
||||
if (isset($auth_key))
|
||||
{
|
||||
$add_url_params['auth'] = $auth_key;
|
||||
}
|
||||
|
||||
if (!isset($cache['cat_names']))
|
||||
{
|
||||
$query = '
|
||||
SELECT id, name, permalink
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
;';
|
||||
$cache['cat_names'] = query2array($query, 'id');
|
||||
}
|
||||
|
||||
$output = '';
|
||||
if ($single_link)
|
||||
{
|
||||
$uppercats_array = explode(',', $uppercats);
|
||||
$single_url = add_url_params(get_root_url().$url.array_pop($uppercats_array), $add_url_params);
|
||||
$output.= '<a href="'.$single_url.'"';
|
||||
if (isset($link_class))
|
||||
{
|
||||
$output.= ' class="'.$link_class.'"';
|
||||
}
|
||||
$output.= '>';
|
||||
}
|
||||
$is_first = true;
|
||||
foreach (explode(',', $uppercats) as $category_id)
|
||||
{
|
||||
$cat = $cache['cat_names'][$category_id];
|
||||
|
||||
$cat['name'] = trigger_change(
|
||||
'render_category_name',
|
||||
$cat['name'],
|
||||
'get_cat_display_name_cache'
|
||||
);
|
||||
|
||||
if ($is_first)
|
||||
{
|
||||
$is_first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$output.= '<span>'.$conf['level_separator'].'</span>';
|
||||
}
|
||||
|
||||
if ( !isset($url) or $single_link )
|
||||
{
|
||||
$output.= $cat['name'];
|
||||
}
|
||||
elseif ($url == '')
|
||||
{
|
||||
$output.= '
|
||||
<a href="'
|
||||
.add_url_params(
|
||||
make_index_url(
|
||||
array(
|
||||
'category' => $cat,
|
||||
)
|
||||
),
|
||||
$add_url_params
|
||||
)
|
||||
.'">'.$cat['name'].'</a>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$output.= '
|
||||
<a href="'.PHPWG_ROOT_PATH.$url.$category_id.'">'.$cat['name'].'</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($single_link and isset($single_url))
|
||||
{
|
||||
$output.= '</a>';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates breadcrumb for a category.
|
||||
* @see get_cat_display_name()
|
||||
*
|
||||
* @param int $cat_id
|
||||
* @param string|null $url
|
||||
* @return string
|
||||
*/
|
||||
function get_cat_display_name_from_id($cat_id, $url = '')
|
||||
{
|
||||
$cat_info = get_cat_info($cat_id);
|
||||
return get_cat_display_name($cat_info['upper_names'], $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply basic markdown transformations to a text.
|
||||
* newlines becomes br tags
|
||||
* _word_ becomes underline
|
||||
* /word/ becomes italic
|
||||
* *word* becomes bolded
|
||||
* urls becomes a tags
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
function render_comment_content($content)
|
||||
{
|
||||
$content = htmlspecialchars($content);
|
||||
$pattern = '/(https?:\/\/\S*)/';
|
||||
$replacement = '<a href="$1" rel="nofollow">$1</a>';
|
||||
$content = preg_replace($pattern, $replacement, $content);
|
||||
|
||||
$content = nl2br($content);
|
||||
|
||||
// replace _word_ by an underlined word
|
||||
$pattern = '/\b_(\S*)_\b/';
|
||||
$replacement = '<span style="text-decoration:underline;">$1</span>';
|
||||
$content = preg_replace($pattern, $replacement, $content);
|
||||
|
||||
// replace *word* by a bolded word
|
||||
$pattern = '/\b\*(\S*)\*\b/';
|
||||
$replacement = '<span style="font-weight:bold;">$1</span>';
|
||||
$content = preg_replace($pattern, $replacement, $content);
|
||||
|
||||
// replace /word/ by an italic word
|
||||
$pattern = "/\/(\S*)\/(\s)/";
|
||||
$replacement = '<span style="font-style:italic;">$1$2</span>';
|
||||
$content = preg_replace($pattern, $replacement, $content);
|
||||
|
||||
// TODO : add a trigger
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback used for sorting by name.
|
||||
*/
|
||||
function name_compare($a, $b)
|
||||
{
|
||||
return strcmp(strtolower($a['name']), strtolower($b['name']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used for sorting by name (slug) with cache.
|
||||
*/
|
||||
function tag_alpha_compare($a, $b)
|
||||
{
|
||||
global $cache;
|
||||
|
||||
foreach (array($a, $b) as $tag)
|
||||
{
|
||||
if (!isset($cache[__FUNCTION__][ $tag['name'] ]))
|
||||
{
|
||||
$cache[__FUNCTION__][ $tag['name'] ] = pwg_transliterate($tag['name']);
|
||||
}
|
||||
}
|
||||
|
||||
return strcmp($cache[__FUNCTION__][ $a['name'] ], $cache[__FUNCTION__][ $b['name'] ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the current script.
|
||||
*/
|
||||
function access_denied()
|
||||
{
|
||||
global $user, $conf;
|
||||
|
||||
if ( isset($user) and !is_a_guest() )
|
||||
{
|
||||
set_status_header(401);
|
||||
|
||||
echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="themes/default/icon/favicon.ico">
|
||||
<div style="display: flex; justify-content: center;align-items: center;height: 100vh;margin: 0;color: #3C3C3C;font-family: \'Open Sans\', sans-serif;font-size: 20px;font-style: normal;font-weight: 600;line-height: normal;">
|
||||
<div style="text-align:center;">
|
||||
<img src="themes/default/icon/warning-triangle.svg" alt="warning-triangle" >
|
||||
<p style="max-width: 400px; margin-top 20px;">'.l10n('You are not authorized to access the requested page').'</p>
|
||||
<a href="'.make_index_url().'" style="display: inline-block;padding: 10px 20px;margin: 10px;margin-top: 50px;border-radius: 7px;cursor: pointer;width: 150px;background-color: #F77000;color: #fff;text-decoration: none;border: 2px solid #F77000;">'.l10n('Home').'</a>
|
||||
</div>
|
||||
</div>';
|
||||
exit();
|
||||
}
|
||||
|
||||
redirect_http(get_root_url().'identification.php?redirect='.urlencode(urlencode($_SERVER['REQUEST_URI'])));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exits the current script with 403 code.
|
||||
* @todo nice display if $template loaded
|
||||
*
|
||||
* @param string $msg
|
||||
* @param string|null $alternate_url redirect to this url
|
||||
*/
|
||||
function page_forbidden($msg, $alternate_url=null)
|
||||
{
|
||||
set_status_header(403);
|
||||
if ($alternate_url==null)
|
||||
$alternate_url = make_index_url();
|
||||
redirect_html( $alternate_url,
|
||||
'<div style="text-align:left; margin-left:5em;margin-bottom:5em;">
|
||||
<h1 style="text-align:left; font-size:36px;">'.l10n('Forbidden').'</h1><br>'
|
||||
.$msg.'</div>',
|
||||
5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the current script with 400 code.
|
||||
* @todo nice display if $template loaded
|
||||
*
|
||||
* @param string $msg
|
||||
* @param string|null $alternate_url redirect to this url
|
||||
*/
|
||||
function bad_request($msg, $alternate_url=null)
|
||||
{
|
||||
set_status_header(400);
|
||||
if ($alternate_url==null)
|
||||
$alternate_url = make_index_url();
|
||||
redirect_html( $alternate_url,
|
||||
'<div style="text-align:left; margin-left:5em;margin-bottom:5em;">
|
||||
<h1 style="text-align:left; font-size:36px;">'.l10n('Bad request').'</h1><br>'
|
||||
.$msg.'</div>',
|
||||
5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the current script with 404 code.
|
||||
* @todo nice display if $template loaded
|
||||
*
|
||||
* @param string $msg
|
||||
* @param string|null $alternate_url redirect to this url
|
||||
*/
|
||||
function page_not_found($msg, $alternate_url=null)
|
||||
{
|
||||
set_status_header(404);
|
||||
if ($alternate_url==null)
|
||||
$alternate_url = make_index_url();
|
||||
redirect_html( $alternate_url,
|
||||
'<div style="text-align:left; margin-left:5em;margin-bottom:5em;">
|
||||
<h1 style="text-align:left; font-size:36px;">'.l10n('Page not found').'</h1><br>'
|
||||
.$msg.'</div>',
|
||||
5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the current script with 500 code.
|
||||
* @todo nice display if $template loaded
|
||||
*
|
||||
* @param string $msg
|
||||
* @param string|null $title
|
||||
* @param bool $show_trace
|
||||
*/
|
||||
function fatal_error($msg, $title=null, $show_trace=true)
|
||||
{
|
||||
if (empty($title))
|
||||
{
|
||||
$title = l10n('Piwigo encountered a non recoverable error');
|
||||
}
|
||||
|
||||
$btrace_msg = '';
|
||||
if ($show_trace and function_exists('debug_backtrace'))
|
||||
{
|
||||
$bt = debug_backtrace();
|
||||
for ($i=1; $i<count($bt); $i++)
|
||||
{
|
||||
$class = isset($bt[$i]['class']) ? (@$bt[$i]['class'].'::') : '';
|
||||
$btrace_msg .= "#$i\t".$class.@$bt[$i]['function'].' '.@$bt[$i]['file']."(".@$bt[$i]['line'].")\n";
|
||||
}
|
||||
$btrace_msg = trim($btrace_msg);
|
||||
$msg .= "\n";
|
||||
}
|
||||
|
||||
$display = "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
|
||||
<h1>$title</h1>
|
||||
<pre style='font-size:larger;background:white;color:red;padding:1em;margin:0;clear:both;display:block;width:auto;height:auto;overflow:auto'>
|
||||
<b>$msg</b>
|
||||
$btrace_msg
|
||||
</pre>\n";
|
||||
|
||||
@set_status_header(500);
|
||||
echo $display.str_repeat( ' ', 300); //IE6 doesn't error output if below a size
|
||||
|
||||
if ( function_exists('ini_set') )
|
||||
{// if possible turn off error display (we display it)
|
||||
ini_set('display_errors', false);
|
||||
}
|
||||
error_reporting( E_ALL );
|
||||
trigger_error( strip_tags($msg).$btrace_msg, E_USER_ERROR );
|
||||
die(0); // just in case
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb to be displayed above thumbnails on tag page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_tags_content_title()
|
||||
{
|
||||
global $page;
|
||||
$title = '<a href="'.get_root_url().'tags.php" title="'.l10n('display available tags').'">'
|
||||
. l10n( count($page['tags']) > 1 ? 'Tags' : 'Tag' )
|
||||
. '</a> ';
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb to be displayed above thumbnails on combined categories page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_combined_categories_content_title()
|
||||
{
|
||||
global $page;
|
||||
|
||||
$title = l10n('Albums').' ';
|
||||
|
||||
$is_first = true;
|
||||
$all_categories = array_merge(array($page['category']), $page['combined_categories']);
|
||||
foreach ($all_categories as $idx => $category)
|
||||
{
|
||||
$title.= $is_first ? '' : ' + ';
|
||||
$is_first = false;
|
||||
|
||||
$title.= get_cat_display_name(array($category));
|
||||
|
||||
if (count($all_categories) > 1) // should be always the case
|
||||
{
|
||||
$other_cats = $all_categories;
|
||||
unset($other_cats[$idx]);
|
||||
|
||||
$params = array(
|
||||
'category' => array_shift($other_cats),
|
||||
);
|
||||
|
||||
if (count($other_cats) > 0)
|
||||
{
|
||||
$params['combined_categories'] = $other_cats;
|
||||
}
|
||||
$remove_url = make_index_url($params);
|
||||
|
||||
$title.=
|
||||
'<a id="TagsGroupRemoveTag" href="'.$remove_url.'" style="border:none;" title="'
|
||||
.l10n('remove this tag from the list')
|
||||
.'"><img src="'
|
||||
.get_root_url().get_themeconf('icon_dir').'/remove_s.png'
|
||||
.'" alt="x" style="vertical-align:bottom;" >'
|
||||
.'<span class="pwg-icon pwg-icon-close" ></span>'
|
||||
.'</a>';
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the http status header (200,401,...)
|
||||
* @param int $code
|
||||
* @param string $text for exotic http codes
|
||||
*/
|
||||
function set_status_header($code, $text='')
|
||||
{
|
||||
if (empty($text))
|
||||
{
|
||||
switch ($code)
|
||||
{
|
||||
case 200: $text='OK';break;
|
||||
case 301: $text='Moved permanently';break;
|
||||
case 302: $text='Moved temporarily';break;
|
||||
case 304: $text='Not modified';break;
|
||||
case 400: $text='Bad request';break;
|
||||
case 401: $text='Authorization required';break;
|
||||
case 403: $text='Forbidden';break;
|
||||
case 404: $text='Not found';break;
|
||||
case 500: $text='Server error';break;
|
||||
case 501: $text='Not implemented';break;
|
||||
case 503: $text='Service unavailable';break;
|
||||
}
|
||||
}
|
||||
$protocol = $_SERVER["SERVER_PROTOCOL"];
|
||||
if ( ('HTTP/1.1' != $protocol) && ('HTTP/1.0' != $protocol) )
|
||||
$protocol = 'HTTP/1.0';
|
||||
|
||||
header( "$protocol $code $text", true, $code );
|
||||
trigger_notify('set_status_header', $code, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category comment for rendering in html textual mode (subcatify)
|
||||
* This method is called by a trigger_notify()
|
||||
*
|
||||
* @param string $desc
|
||||
* @return string
|
||||
*/
|
||||
function render_category_literal_description($desc)
|
||||
{
|
||||
!isset($desc) ? $desc = "" : false;
|
||||
return strip_tags($desc, '<span><p><a><br><b><i><small><big><strong><em>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add known menubar blocks.
|
||||
* This method is called by a trigger_change()
|
||||
*
|
||||
* @param BlockManager[] $menu_ref_arr
|
||||
*/
|
||||
function register_default_menubar_blocks($menu_ref_arr)
|
||||
{
|
||||
$menu = & $menu_ref_arr[0];
|
||||
if ($menu->get_id() != 'menubar')
|
||||
return;
|
||||
$menu->register_block( new RegisteredBlock( 'mbLinks', 'Links', 'piwigo'));
|
||||
$menu->register_block( new RegisteredBlock( 'mbCategories', 'Albums', 'piwigo'));
|
||||
$menu->register_block( new RegisteredBlock( 'mbTags', 'Tags', 'piwigo'));
|
||||
$menu->register_block( new RegisteredBlock( 'mbSpecials', 'Specials', 'piwigo'));
|
||||
$menu->register_block( new RegisteredBlock( 'mbMenu', 'Menu', 'piwigo'));
|
||||
$menu->register_block( new RegisteredBlock( 'mbRelatedCategories', 'Related albums', 'piwigo') );
|
||||
|
||||
// We hide the quick identification menu on the identification page. It
|
||||
// would be confusing.
|
||||
if (script_basename() != 'identification')
|
||||
{
|
||||
$menu->register_block( new RegisteredBlock( 'mbIdentification', 'Identification', 'piwigo') );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display name for an element.
|
||||
* Returns 'name' if exists of name from 'file'.
|
||||
*
|
||||
* @param array $info at least file or name
|
||||
* @return string
|
||||
*/
|
||||
function render_element_name($info)
|
||||
{
|
||||
if (!empty($info['name']))
|
||||
{
|
||||
return trigger_change('render_element_name', $info['name'], $info);
|
||||
}
|
||||
return get_name_from_file($info['file']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display description for an element.
|
||||
*
|
||||
* @param array $info at least comment
|
||||
* @param string $param used to identify the trigger
|
||||
* @return string
|
||||
*/
|
||||
function render_element_description($info, $param='')
|
||||
{
|
||||
if (!empty($info['comment']))
|
||||
{
|
||||
return trigger_change('render_element_description', $info['comment'], $param);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add info to the title of the thumbnail based on photo properties.
|
||||
*
|
||||
* @param array $info hit, rating_score, nb_comments
|
||||
* @param string $title
|
||||
* @param string $comment
|
||||
* @return string
|
||||
*/
|
||||
function get_thumbnail_title($info, $title, $comment='')
|
||||
{
|
||||
global $conf, $user;
|
||||
|
||||
$details = array();
|
||||
|
||||
if (!empty($info['hit']))
|
||||
{
|
||||
$details[] = l10n('%d visits', $info['hit']);
|
||||
}
|
||||
|
||||
if ($conf['rate'] and !empty($info['rating_score']))
|
||||
{
|
||||
$details[] = l10n('rating score %s', $info['rating_score']);
|
||||
}
|
||||
|
||||
if (isset($info['nb_comments']) and $info['nb_comments'] != 0)
|
||||
{
|
||||
$details[] = l10n_dec('%d comment', '%d comments', $info['nb_comments']);
|
||||
}
|
||||
|
||||
if (count($details) > 0)
|
||||
{
|
||||
$title.= ' ('.implode(', ', $details).')';
|
||||
}
|
||||
|
||||
if (!empty($comment))
|
||||
{
|
||||
$comment = strip_tags($comment);
|
||||
$title.= ' '.substr($comment, 0, 100).(strlen($comment) > 100 ? '...' : '');
|
||||
}
|
||||
|
||||
$title = htmlspecialchars(strip_tags($title));
|
||||
$title = trigger_change('get_thumbnail_title', $title, $info);
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to protect src image urls.
|
||||
*
|
||||
* @param string $url
|
||||
* @param SrcImage $src_image
|
||||
* @return string
|
||||
*/
|
||||
function get_src_image_url_protection_handler($url, $src_image)
|
||||
{
|
||||
return get_action_url($src_image->id, $src_image->is_original() ? 'e' : 'r', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to protect element urls.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $infos id, path
|
||||
* @return string
|
||||
*/
|
||||
function get_element_url_protection_handler($url, $infos)
|
||||
{
|
||||
global $conf;
|
||||
if ('images'==$conf['original_url_protection'])
|
||||
{// protect only images and not other file types (for example large movies that we don't want to send through our file proxy)
|
||||
$ext = get_extension($infos['path']);
|
||||
if (!in_array($ext, $conf['picture_ext']))
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
return get_action_url($infos['id'], 'e', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends to the template all messages stored in $page and in the session.
|
||||
*/
|
||||
function flush_page_messages()
|
||||
{
|
||||
global $template, $page;
|
||||
if ($template->get_template_vars('page_refresh') === null)
|
||||
{
|
||||
foreach (array('errors','infos','warnings', 'messages') as $mode)
|
||||
{
|
||||
if (isset($_SESSION['page_'.$mode]))
|
||||
{
|
||||
$page[$mode] = array_merge($page[$mode], $_SESSION['page_'.$mode]);
|
||||
unset($_SESSION['page_'.$mode]);
|
||||
}
|
||||
|
||||
if (!empty($page[$mode]))
|
||||
{
|
||||
$template->assign($mode, $page[$mode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pwg_nl2br is useful for PHP 5.2 which doesn't accept more than 1
|
||||
* parameter on nl2br() (and anyway the second parameter of nl2br does not
|
||||
* match what Piwigo gives.
|
||||
*/
|
||||
function pwg_nl2br($string)
|
||||
{
|
||||
if (empty($string))
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
return nl2br($string);
|
||||
}
|
||||
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\metadata
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* returns informations from IPTC metadata, mapping is done in this function.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param array $map
|
||||
* @return array
|
||||
*/
|
||||
function get_iptc_data($filename, $map, $array_sep=',')
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$result = array();
|
||||
|
||||
$imginfo = array();
|
||||
if (false == @getimagesize($filename, $imginfo) )
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (isset($imginfo['APP13']))
|
||||
{
|
||||
$iptc = iptcparse($imginfo['APP13']);
|
||||
if (is_array($iptc))
|
||||
{
|
||||
$rmap = array_flip($map);
|
||||
foreach (array_keys($rmap) as $iptc_key)
|
||||
{
|
||||
if (isset($iptc[$iptc_key][0]))
|
||||
{
|
||||
if ($iptc_key == '2#025')
|
||||
{
|
||||
$value = implode($array_sep,
|
||||
array_map('clean_iptc_value',$iptc[$iptc_key]));
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = clean_iptc_value($iptc[$iptc_key][0]);
|
||||
}
|
||||
|
||||
foreach (array_keys($map, $iptc_key) as $pwg_key)
|
||||
{
|
||||
$result[$pwg_key] = $value;
|
||||
|
||||
if (!$conf['allow_html_in_metadata'])
|
||||
{
|
||||
// in case the origin of the photo is unsecure (user upload), we
|
||||
// remove HTML tags to avoid XSS (malicious execution of
|
||||
// javascript)
|
||||
$result[$pwg_key] = strip_tags($result[$pwg_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a cleaned IPTC value.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
function clean_iptc_value($value)
|
||||
{
|
||||
// strip leading zeros (weird Kodak Scanner software)
|
||||
while ( isset($value[0]) and $value[0] == chr(0))
|
||||
{
|
||||
$value = substr($value, 1);
|
||||
}
|
||||
// remove binary nulls
|
||||
$value = str_replace(chr(0x00), ' ', $value);
|
||||
|
||||
if ( preg_match('/[\x80-\xff]/', $value) )
|
||||
{
|
||||
// apparently mac uses some MacRoman crap encoding. I don't know
|
||||
// how to detect it so a plugin should do the trick.
|
||||
$value = trigger_change('clean_iptc_value', $value);
|
||||
if ( ($qual = qualify_utf8($value)) != 0)
|
||||
{// has non ascii chars
|
||||
if ($qual>0)
|
||||
{
|
||||
$input_encoding = 'utf-8';
|
||||
}
|
||||
else
|
||||
{
|
||||
$input_encoding = 'iso-8859-1';
|
||||
if (function_exists('iconv') or function_exists('mb_convert_encoding'))
|
||||
{
|
||||
// using windows-1252 because it supports additional characters
|
||||
// such as "oe" in a single character (ligature). About the
|
||||
// difference between Windows-1252 and ISO-8859-1: the characters
|
||||
// 0x80-0x9F will not convert correctly. But these are control
|
||||
// characters which are almost never used.
|
||||
$input_encoding = 'windows-1252';
|
||||
}
|
||||
}
|
||||
|
||||
$value = convert_charset($value, $input_encoding, get_pwg_charset());
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns informations from EXIF metadata, mapping is done in this function.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param array $map
|
||||
* @return array
|
||||
*/
|
||||
function get_exif_data($filename, $map)
|
||||
{
|
||||
global $conf, $logger;
|
||||
|
||||
$result = array();
|
||||
|
||||
if (!function_exists('exif_read_data'))
|
||||
{
|
||||
die('Exif extension not available, admin should disable exif use');
|
||||
}
|
||||
|
||||
// Read EXIF data
|
||||
if ($exif = @exif_read_data($filename) or $exif2 = trigger_change('format_exif_data', $exif=null, $filename, $map))
|
||||
{
|
||||
if (!empty($exif2))
|
||||
{
|
||||
$exif = $exif2;
|
||||
}
|
||||
else
|
||||
{
|
||||
$exif = trigger_change('format_exif_data', $exif, $filename, $map);
|
||||
}
|
||||
|
||||
// configured fields
|
||||
foreach ($map as $key => $field)
|
||||
{
|
||||
if (strpos($field, ';') === false)
|
||||
{
|
||||
if (isset($exif[$field]))
|
||||
{
|
||||
$result[$key] = $exif[$field];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = explode(';', $field);
|
||||
if (isset($exif[$tokens[0]][$tokens[1]]))
|
||||
{
|
||||
$result[$key] = $exif[$tokens[0]][$tokens[1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPS data
|
||||
$gps_exif = array_intersect_key($exif, array_flip(array('GPSLatitudeRef', 'GPSLatitude', 'GPSLongitudeRef', 'GPSLongitude')));
|
||||
if (count($gps_exif) == 4)
|
||||
{
|
||||
if (
|
||||
is_array($gps_exif['GPSLatitude']) and in_array($gps_exif['GPSLatitudeRef'], array('S', 'N')) and
|
||||
is_array($gps_exif['GPSLongitude']) and in_array($gps_exif['GPSLongitudeRef'], array('W', 'E'))
|
||||
)
|
||||
{
|
||||
$latitude = parse_exif_gps_data($gps_exif['GPSLatitude'], $gps_exif['GPSLatitudeRef']);
|
||||
$longitude = parse_exif_gps_data($gps_exif['GPSLongitude'], $gps_exif['GPSLongitudeRef']);
|
||||
|
||||
if ($latitude >= -90.0 && $latitude <= 90.0 && $longitude >= -180.0 && $longitude <= 180.0)
|
||||
{
|
||||
$result['latitude'] = $latitude;
|
||||
$result['longitude'] = $longitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger->info('['.__FUNCTION__.'][filename='.$filename.'] invalid GPS coordinates, latitude='.$latitude.' longitude='.$longitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$conf['allow_html_in_metadata'])
|
||||
{
|
||||
foreach ($result as $key => $value)
|
||||
{
|
||||
// in case the origin of the photo is unsecure (user upload), we remove
|
||||
// HTML tags to avoid XSS (malicious execution of javascript)
|
||||
if (is_array($value))
|
||||
{
|
||||
array_walk_recursive($value, 'strip_html_in_metadata');
|
||||
}
|
||||
else
|
||||
{
|
||||
$result[$key] = strip_tags($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function strip_html_in_metadata(&$v, $k)
|
||||
{
|
||||
$v = strip_tags($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts EXIF GPS format to a float value.
|
||||
* @since 2.6
|
||||
*
|
||||
* @param string[] $raw eg:
|
||||
* - 41/1
|
||||
* - 54/1
|
||||
* - 9843/500
|
||||
* @param string $ref 'S', 'N', 'E', 'W'. eg: 'N'
|
||||
* @return float eg: 41.905468
|
||||
*/
|
||||
function parse_exif_gps_data($raw, $ref)
|
||||
{
|
||||
foreach ($raw as &$i)
|
||||
{
|
||||
$i = explode('/', $i);
|
||||
$i = $i[1]==0 ? 0 : $i[0]/$i[1];
|
||||
}
|
||||
unset($i);
|
||||
|
||||
$v = $raw[0] + $raw[1]/60 + $raw[2]/3600;
|
||||
|
||||
$ref = strtoupper($ref);
|
||||
if ($ref == 'S' or $ref == 'W') $v= -$v;
|
||||
|
||||
return $v;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\notification
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Get standard sql where in order to restrict and filter categories and images.
|
||||
* IMAGE_CATEGORY_TABLE must be named "ic" in the query
|
||||
*
|
||||
* @param string $prefix_condition
|
||||
* @param string $img_field
|
||||
* @param bool $force_one_condition
|
||||
* @return string
|
||||
*/
|
||||
function get_std_sql_where_restrict_filter($prefix_condition,
|
||||
$img_field = 'ic.image_id',
|
||||
$force_one_condition = false)
|
||||
{
|
||||
return get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'ic.category_id',
|
||||
'visible_categories' => 'ic.category_id',
|
||||
'visible_images' => $img_field
|
||||
),
|
||||
$prefix_condition,
|
||||
$force_one_condition
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute custom notification query.
|
||||
* @todo use a cache for all data returned by custom_notification_query()
|
||||
*
|
||||
* @param string $action 'count', 'info'
|
||||
* @param string $type 'new_comments', 'unvalidated_comments', 'new_elements', 'updated_categories', 'new_users'
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int|array int for action count array for info
|
||||
*/
|
||||
function custom_notification_query($action, $type, $start=null, $end=null)
|
||||
{
|
||||
global $user;
|
||||
|
||||
switch($type)
|
||||
{
|
||||
case 'new_comments':
|
||||
{
|
||||
$query = '
|
||||
FROM '.COMMENTS_TABLE.' AS c
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON c.image_id = ic.image_id
|
||||
WHERE 1=1';
|
||||
if (!empty($start))
|
||||
{
|
||||
$query.= '
|
||||
AND c.validation_date > \''.$start.'\'';
|
||||
}
|
||||
if (!empty($end))
|
||||
{
|
||||
$query.= '
|
||||
AND c.validation_date <= \''.$end.'\'';
|
||||
}
|
||||
$query.= get_std_sql_where_restrict_filter('AND');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unvalidated_comments':
|
||||
{
|
||||
$query = '
|
||||
FROM '.COMMENTS_TABLE.'
|
||||
WHERE 1=1';
|
||||
if (!empty($start))
|
||||
{
|
||||
$query.= '
|
||||
AND date > \''.$start.'\'';
|
||||
}
|
||||
if (!empty($end))
|
||||
{
|
||||
$query.= '
|
||||
AND date <= \''.$end.'\'';
|
||||
}
|
||||
$query.= '
|
||||
AND validated = \'false\'';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'new_elements':
|
||||
{
|
||||
$query = '
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON image_id = id
|
||||
WHERE 1=1';
|
||||
if (!empty($start))
|
||||
{
|
||||
$query.= '
|
||||
AND date_available > \''.$start.'\'';
|
||||
}
|
||||
if (!empty($end))
|
||||
{
|
||||
$query.= '
|
||||
AND date_available <= \''.$end.'\'';
|
||||
}
|
||||
$query.= get_std_sql_where_restrict_filter('AND', 'id');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updated_categories':
|
||||
{
|
||||
$query = '
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON image_id = id
|
||||
WHERE 1=1';
|
||||
if (!empty($start))
|
||||
{
|
||||
$query.= '
|
||||
AND date_available > \''.$start.'\'';
|
||||
}
|
||||
if (!empty($end))
|
||||
{
|
||||
$query.= '
|
||||
AND date_available <= \''.$end.'\'';
|
||||
}
|
||||
$query.= get_std_sql_where_restrict_filter('AND', 'id');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'new_users':
|
||||
{
|
||||
$query = '
|
||||
FROM '.USER_INFOS_TABLE.'
|
||||
WHERE 1=1';
|
||||
if (!empty($start))
|
||||
{
|
||||
$query.= '
|
||||
AND registration_date > \''.$start.'\'';
|
||||
}
|
||||
if (!empty($end))
|
||||
{
|
||||
$query.= '
|
||||
AND registration_date <= \''.$end.'\'';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return null; // stop and return nothing
|
||||
}
|
||||
|
||||
switch($action)
|
||||
{
|
||||
case 'count':
|
||||
{
|
||||
switch($type)
|
||||
{
|
||||
case 'new_comments':
|
||||
$field_id = 'c.id';
|
||||
break;
|
||||
case 'unvalidated_comments':
|
||||
$field_id = 'id';
|
||||
break;
|
||||
case 'new_elements':
|
||||
$field_id = 'image_id';
|
||||
break;
|
||||
case 'updated_categories':
|
||||
$field_id = 'category_id';
|
||||
break;
|
||||
case 'new_users':
|
||||
$field_id = 'user_id';
|
||||
break;
|
||||
}
|
||||
$query = 'SELECT COUNT(DISTINCT '.$field_id.') '.$query.';';
|
||||
list($count) = pwg_db_fetch_row(pwg_query($query));
|
||||
return $count;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'info':
|
||||
{
|
||||
switch($type)
|
||||
{
|
||||
case 'new_comments':
|
||||
$field_id = 'c.id';
|
||||
break;
|
||||
case 'unvalidated_comments':
|
||||
$field_id = 'id';
|
||||
break;
|
||||
case 'new_elements':
|
||||
$field_id = 'image_id';
|
||||
break;
|
||||
case 'updated_categories':
|
||||
$field_id = 'category_id';
|
||||
break;
|
||||
case 'new_users':
|
||||
$field_id = 'user_id';
|
||||
break;
|
||||
}
|
||||
$query = 'SELECT DISTINCT '.$field_id.' '.$query.';';
|
||||
$infos = query2array($query);
|
||||
return $infos;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return null; // stop and return nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of new comments between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int
|
||||
*/
|
||||
function nb_new_comments($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('count', 'new_comments', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new comments between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int[] comment ids
|
||||
*/
|
||||
function new_comments($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('info', 'new_comments', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of unvalidated comments between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int
|
||||
*/
|
||||
function nb_unvalidated_comments($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('count', 'unvalidated_comments', $start, $end);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns number of new photos between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int
|
||||
*/
|
||||
function nb_new_elements($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('count', 'new_elements', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new photos between two dates.es
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int[] photos ids
|
||||
*/
|
||||
function new_elements($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('info', 'new_elements', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of updated categories between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int
|
||||
*/
|
||||
function nb_updated_categories($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('count', 'updated_categories', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns updated categories between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int[] categories ids
|
||||
*/
|
||||
function updated_categories($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('info', 'updated_categories', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of new users between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int
|
||||
*/
|
||||
function nb_new_users($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('count', 'new_users', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new users between two dates.
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return int[] user ids
|
||||
*/
|
||||
function new_users($start=null, $end=null)
|
||||
{
|
||||
return custom_notification_query('info', 'new_users', $start, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if there was new activity between two dates.
|
||||
*
|
||||
* Takes in account: number of new comments, number of new elements, number of
|
||||
* updated categories. Administrators are also informed about: number of
|
||||
* unvalidated comments, number of new users.
|
||||
* @todo number of unvalidated elements
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @return boolean
|
||||
*/
|
||||
function news_exists($start=null, $end=null)
|
||||
{
|
||||
return (
|
||||
(nb_new_comments($start, $end) > 0) or
|
||||
(nb_new_elements($start, $end) > 0) or
|
||||
(nb_updated_categories($start, $end) > 0) or
|
||||
((is_admin()) and (nb_unvalidated_comments($start, $end) > 0)) or
|
||||
((is_admin()) and (nb_new_users($start, $end) > 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a news line and adds it to the array (e.g. '5 new elements')
|
||||
*
|
||||
* @param array &$news
|
||||
* @param int $count
|
||||
* @param string $singular_key
|
||||
* @param string $plural_key
|
||||
* @param string $url
|
||||
* @param bool $add_url
|
||||
*/
|
||||
function add_news_line(&$news, $count, $singular_key, $plural_key, $url='', $add_url=false)
|
||||
{
|
||||
if ($count > 0)
|
||||
{
|
||||
$line = l10n_dec($singular_key, $plural_key, $count);
|
||||
if ($add_url and !empty($url) )
|
||||
{
|
||||
$line = '<a href="'.$url.'">'.$line.'</a>';
|
||||
}
|
||||
$news[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new activity between two dates.
|
||||
*
|
||||
* Takes in account: number of new comments, number of new elements, number of
|
||||
* updated categories. Administrators are also informed about: number of
|
||||
* unvalidated comments, number of new users.
|
||||
* @todo number of unvalidated elements
|
||||
*
|
||||
* @param string $start (mysql datetime format)
|
||||
* @param string $end (mysql datetime format)
|
||||
* @param bool $exclude_img_cats if true, no info about new images/categories
|
||||
* @param bool $add_url add html link around news
|
||||
* @return array
|
||||
*/
|
||||
function news($start=null, $end=null, $exclude_img_cats=false, $add_url=false, $auth_key=null)
|
||||
{
|
||||
$news = array();
|
||||
|
||||
$add_url_params = array();
|
||||
if (isset($auth_key))
|
||||
{
|
||||
$add_url_params['auth'] = $auth_key;
|
||||
}
|
||||
|
||||
if (!$exclude_img_cats)
|
||||
{
|
||||
add_news_line(
|
||||
$news,
|
||||
nb_new_elements($start, $end),
|
||||
'%d new photo',
|
||||
'%d new photos',
|
||||
add_url_params(make_index_url(array('section'=>'recent_pics')), $add_url_params),
|
||||
$add_url
|
||||
);
|
||||
|
||||
add_news_line(
|
||||
$news,
|
||||
nb_updated_categories($start, $end),
|
||||
'%d album updated',
|
||||
'%d albums updated',
|
||||
add_url_params(make_index_url(array('section'=>'recent_cats')), $add_url_params),
|
||||
$add_url
|
||||
);
|
||||
}
|
||||
|
||||
add_news_line(
|
||||
$news,
|
||||
nb_new_comments($start, $end),
|
||||
'%d new comment',
|
||||
'%d new comments',
|
||||
add_url_params(get_root_url().'comments.php', $add_url_params),
|
||||
$add_url
|
||||
);
|
||||
|
||||
if (is_admin())
|
||||
{
|
||||
add_news_line( $news,
|
||||
nb_unvalidated_comments($start, $end), '%d comment to validate', '%d comments to validate',
|
||||
get_root_url().'admin.php?page=comments', $add_url );
|
||||
|
||||
add_news_line( $news,
|
||||
nb_new_users($start, $end), '%d new user', '%d new users',
|
||||
get_root_url().'admin.php?page=user_list', $add_url );
|
||||
}
|
||||
|
||||
return $news;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about recently published elements grouped by post date.
|
||||
*
|
||||
* @param int $max_dates maximum number of recent dates
|
||||
* @param int $max_elements maximum number of elements per date
|
||||
* @param int $max_cats maximum number of categories per date
|
||||
* @return array
|
||||
*/
|
||||
function get_recent_post_dates($max_dates, $max_elements, $max_cats)
|
||||
{
|
||||
global $conf, $user, $persistent_cache;
|
||||
|
||||
$cache_key = $persistent_cache->make_key('recent_posts'.$user['id'].$user['cache_update_time'].$max_dates.$max_elements.$max_cats);
|
||||
if ($persistent_cache->get($cache_key, $cached))
|
||||
{
|
||||
return $cached;
|
||||
}
|
||||
$where_sql = get_std_sql_where_restrict_filter('WHERE', 'i.id', true);
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
date_available,
|
||||
COUNT(DISTINCT id) AS nb_elements,
|
||||
COUNT(DISTINCT category_id) AS nb_cats
|
||||
FROM '.IMAGES_TABLE.' i INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id=image_id
|
||||
'.$where_sql.'
|
||||
GROUP BY date_available
|
||||
ORDER BY date_available DESC
|
||||
LIMIT '.$max_dates.'
|
||||
;';
|
||||
$dates = query2array($query);
|
||||
|
||||
for ($i=0; $i<count($dates); $i++)
|
||||
{
|
||||
if ($max_elements>0)
|
||||
{ // get some thumbnails ...
|
||||
$query = '
|
||||
SELECT DISTINCT i.*
|
||||
FROM '.IMAGES_TABLE.' i
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id=image_id
|
||||
'.$where_sql.'
|
||||
AND date_available=\''.$dates[$i]['date_available'].'\'
|
||||
ORDER BY '.DB_RANDOM_FUNCTION.'()
|
||||
LIMIT '.$max_elements.'
|
||||
;';
|
||||
$dates[$i]['elements'] = query2array($query);
|
||||
}
|
||||
|
||||
if ($max_cats>0)
|
||||
{// get some categories ...
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT c.uppercats,
|
||||
COUNT(DISTINCT i.id) AS img_count
|
||||
FROM '.IMAGES_TABLE.' i
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON i.id=image_id
|
||||
INNER JOIN '.CATEGORIES_TABLE.' c ON c.id=category_id
|
||||
'.$where_sql.'
|
||||
AND date_available=\''.$dates[$i]['date_available'].'\'
|
||||
GROUP BY category_id, c.uppercats
|
||||
ORDER BY img_count DESC
|
||||
LIMIT '.$max_cats.'
|
||||
;';
|
||||
$dates[$i]['categories'] = query2array($query);
|
||||
}
|
||||
}
|
||||
|
||||
$persistent_cache->set($cache_key, $dates);
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about recently published elements grouped by post date.
|
||||
* Same as get_recent_post_dates() but parameters as an indexed array.
|
||||
* @see get_recent_post_dates()
|
||||
*
|
||||
* @param array $args
|
||||
* @return array
|
||||
*/
|
||||
function get_recent_post_dates_array($args)
|
||||
{
|
||||
return get_recent_post_dates(
|
||||
(empty($args['max_dates']) ? 3 : $args['max_dates']),
|
||||
(empty($args['max_elements']) ? 3 : $args['max_elements']),
|
||||
(empty($args['max_cats']) ? 3 : $args['max_cats'])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns html description about recently published elements grouped by post date.
|
||||
* @todo clean up HTML output, currently messy and invalid !
|
||||
*
|
||||
* @param array $date_detail returned value of get_recent_post_dates()
|
||||
* @return string
|
||||
*/
|
||||
function get_html_description_recent_post_date($date_detail, $auth_key=null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$add_url_params = array();
|
||||
if (isset($auth_key))
|
||||
{
|
||||
$add_url_params['auth'] = $auth_key;
|
||||
}
|
||||
|
||||
$description = '<ul>';
|
||||
|
||||
$description .=
|
||||
'<li>'
|
||||
.l10n_dec('%d new photo', '%d new photos', $date_detail['nb_elements'])
|
||||
.' ('
|
||||
.'<a href="'.add_url_params(make_index_url(array('section'=>'recent_pics')), $add_url_params).'">'
|
||||
.l10n('Recent photos').'</a>'
|
||||
.')'
|
||||
.'</li><br>';
|
||||
|
||||
foreach($date_detail['elements'] as $element)
|
||||
{
|
||||
$tn_src = DerivativeImage::thumb_url($element);
|
||||
$description .= '<a href="'.
|
||||
add_url_params(
|
||||
make_picture_url(
|
||||
array(
|
||||
'image_id' => $element['id'],
|
||||
'image_file' => $element['file'],
|
||||
)
|
||||
),
|
||||
$add_url_params
|
||||
)
|
||||
.'"><img src="'.$tn_src.'"></a>';
|
||||
}
|
||||
$description .= '...<br>';
|
||||
|
||||
$description .=
|
||||
'<li>'
|
||||
.l10n_dec('%d album updated', '%d albums updated', $date_detail['nb_cats'])
|
||||
.'</li>';
|
||||
|
||||
$description .= '<ul>';
|
||||
foreach($date_detail['categories'] as $cat)
|
||||
{
|
||||
$description .=
|
||||
'<li>'
|
||||
.get_cat_display_name_cache($cat['uppercats'],'', false, null, $auth_key)
|
||||
.' ('.
|
||||
l10n_dec('%d new photo', '%d new photos', $cat['img_count']).')'
|
||||
.'</li>';
|
||||
}
|
||||
$description .= '</ul>';
|
||||
|
||||
$description .= '</ul>';
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns title about recently published elements grouped by post date.
|
||||
*
|
||||
* @param array $date_detail returned value of get_recent_post_dates()
|
||||
* @return string
|
||||
*/
|
||||
function get_title_recent_post_date($date_detail)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
$title = l10n_dec('%d new photo', '%d new photos', $date_detail['nb_elements']);
|
||||
|
||||
if (preg_match('/^\d+-(\d+)-(\d+) /', $date_detail['date_available'], $matches))
|
||||
{
|
||||
$title .= ' ('.$lang['month'][(int)$matches[1]].' '.$matches[2].')';
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\picture
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns slideshow default params.
|
||||
* - period
|
||||
* - repeat
|
||||
* - play
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_default_slideshow_params()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
return array(
|
||||
'period' => $conf['slideshow_period'],
|
||||
'repeat' => $conf['slideshow_repeat'],
|
||||
'play' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and corrects slideshow params
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
function correct_slideshow_params($params=array())
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if ($params['period'] < $conf['slideshow_period_min'])
|
||||
{
|
||||
$params['period'] = $conf['slideshow_period_min'];
|
||||
}
|
||||
else if ($params['period'] > $conf['slideshow_period_max'])
|
||||
{
|
||||
$params['period'] = $conf['slideshow_period_max'];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes slideshow string params into array
|
||||
*
|
||||
* @param string $encode_params
|
||||
* @return array
|
||||
*/
|
||||
function decode_slideshow_params($encode_params=null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$result = get_default_slideshow_params();
|
||||
|
||||
if (is_numeric($encode_params))
|
||||
{
|
||||
$result['period'] = $encode_params;
|
||||
}
|
||||
else
|
||||
{
|
||||
$matches = array();
|
||||
if (preg_match_all('/([a-z]+)-(\d+)/', $encode_params, $matches))
|
||||
{
|
||||
$matchcount = count($matches[1]);
|
||||
for ($i = 0; $i < $matchcount; $i++)
|
||||
{
|
||||
$result[$matches[1][$i]] = $matches[2][$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all('/([a-z]+)-(true|false)/', $encode_params, $matches))
|
||||
{
|
||||
$matchcount = count($matches[1]);
|
||||
for ($i = 0; $i < $matchcount; $i++)
|
||||
{
|
||||
$result[$matches[1][$i]] = get_boolean($matches[2][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return correct_slideshow_params($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes slideshow array params into a string
|
||||
*
|
||||
* @param array $decode_params
|
||||
* @return string
|
||||
*/
|
||||
function encode_slideshow_params($decode_params=array())
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$params = array_diff_assoc(correct_slideshow_params($decode_params), get_default_slideshow_params());
|
||||
$result = '';
|
||||
|
||||
foreach ($params as $name => $value)
|
||||
{
|
||||
// boolean_to_string return $value, if it's not a bool
|
||||
$result .= '+'.$name.'-'.boolean_to_string($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the number of visits for a given photo.
|
||||
*
|
||||
* Code moved from picture.php to be used by both the API and picture.php
|
||||
*
|
||||
* @since 14
|
||||
* @param int $image_id
|
||||
*/
|
||||
function increase_image_visit_counter($image_id)
|
||||
{
|
||||
// avoiding auto update of "lastmodified" field
|
||||
$query = '
|
||||
UPDATE
|
||||
'.IMAGES_TABLE.'
|
||||
SET hit = hit+1, lastmodified = lastmodified
|
||||
WHERE id = '.$image_id.'
|
||||
;';
|
||||
pwg_query($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pages of a PDF file
|
||||
*
|
||||
* @param string $pdfPath
|
||||
* @return int
|
||||
*/
|
||||
function count_pdf_pages($pdfPath)
|
||||
{
|
||||
$pdftext = file_get_contents($pdfPath);
|
||||
$num = preg_match_all("/\/Page\W/", $pdftext, $dummy);
|
||||
|
||||
return $num;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,447 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\plugins
|
||||
*/
|
||||
|
||||
|
||||
/** base directory of plugins */
|
||||
define('PHPWG_PLUGINS_PATH', PHPWG_ROOT_PATH.'plugins/');
|
||||
/** default priority for plugins handlers */
|
||||
define('EVENT_HANDLER_PRIORITY_NEUTRAL', 50);
|
||||
|
||||
|
||||
/**
|
||||
* Used to declare maintenance methods of a plugin.
|
||||
*/
|
||||
class PluginMaintain
|
||||
{
|
||||
/** @var string $plugin_id */
|
||||
protected $plugin_id;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
function __construct($id)
|
||||
{
|
||||
$this->plugin_id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin_version
|
||||
* @param array &$errors - used to return error messages
|
||||
*/
|
||||
function install($plugin_version, &$errors=array()) {}
|
||||
|
||||
/**
|
||||
* @param string $plugin_version
|
||||
* @param array &$errors - used to return error messages
|
||||
*/
|
||||
function activate($plugin_version, &$errors=array()) {}
|
||||
|
||||
function deactivate() {}
|
||||
|
||||
function uninstall() {}
|
||||
|
||||
/**
|
||||
* @param string $old_version
|
||||
* @param string $new_version
|
||||
* @param array &$errors - used to return error messages
|
||||
*/
|
||||
function update($old_version, $new_version, &$errors=array()) {}
|
||||
|
||||
/**
|
||||
* @removed 2.7
|
||||
*/
|
||||
function autoUpdate()
|
||||
{
|
||||
if (is_admin() && !defined('IN_WS'))
|
||||
{
|
||||
trigger_error('Function PluginMaintain::autoUpdate deprecated', E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to declare maintenance methods of a theme.
|
||||
*/
|
||||
class ThemeMaintain
|
||||
{
|
||||
/** @var string $theme_id */
|
||||
protected $theme_id;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
function __construct($id)
|
||||
{
|
||||
$this->theme_id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $theme_version
|
||||
* @param array &$errors - used to return error messages
|
||||
*/
|
||||
function activate($theme_version, &$errors=array()) {}
|
||||
|
||||
function deactivate() {}
|
||||
|
||||
function delete() {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register an event handler.
|
||||
*
|
||||
* @param string $event the name of the event to listen to
|
||||
* @param Callable $func the callback function
|
||||
* @param int $priority greater priority will be executed at last
|
||||
* @param string $include_path file to include before executing the callback
|
||||
* @return bool false is handler already exists
|
||||
*/
|
||||
function add_event_handler($event, $func,
|
||||
$priority=EVENT_HANDLER_PRIORITY_NEUTRAL, $include_path=null)
|
||||
{
|
||||
global $pwg_event_handlers;
|
||||
|
||||
if (isset($pwg_event_handlers[$event][$priority]))
|
||||
{
|
||||
foreach ($pwg_event_handlers[$event][$priority] as $handler)
|
||||
{
|
||||
if ($handler['function'] == $func)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pwg_event_handlers[$event][$priority][] = array(
|
||||
'function' => $func,
|
||||
'include_path' => is_string($include_path) ? $include_path : null,
|
||||
);
|
||||
|
||||
ksort($pwg_event_handlers[$event]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event handler.
|
||||
* @see add_event_handler()
|
||||
*
|
||||
* @param string $event
|
||||
* @param Callable $func
|
||||
* @param int $priority
|
||||
*/
|
||||
function remove_event_handler($event, $func,
|
||||
$priority=EVENT_HANDLER_PRIORITY_NEUTRAL)
|
||||
{
|
||||
global $pwg_event_handlers;
|
||||
|
||||
if (!isset($pwg_event_handlers[$event][$priority]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for ($i=0; $i<count($pwg_event_handlers[$event][$priority]); $i++)
|
||||
{
|
||||
if ($pwg_event_handlers[$event][$priority][$i]['function']==$func)
|
||||
{
|
||||
unset($pwg_event_handlers[$event][$priority][$i]);
|
||||
$pwg_event_handlers[$event][$priority] =
|
||||
array_values($pwg_event_handlers[$event][$priority]);
|
||||
|
||||
if (empty($pwg_event_handlers[$event][$priority]))
|
||||
{
|
||||
unset($pwg_event_handlers[$event][$priority]);
|
||||
if (empty($pwg_event_handlers[$event]))
|
||||
{
|
||||
unset($pwg_event_handlers[$event]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a modifier event and calls all registered event handlers.
|
||||
* trigger_change() is used as a modifier: it allows to transmit _$data_
|
||||
* through all handlers, thus each handler MUST return a value,
|
||||
* optional _$args_ are not transmitted.
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param string $event
|
||||
* @param mixed $data data to transmit to all handlers
|
||||
* @param mixed $args,... optional arguments
|
||||
* @return mixed $data
|
||||
*/
|
||||
function trigger_change($event, $data=null)
|
||||
{
|
||||
global $pwg_event_handlers;
|
||||
|
||||
if (isset($pwg_event_handlers['trigger']))
|
||||
{// debugging
|
||||
trigger_notify('trigger',
|
||||
array('type'=>'event', 'event'=>$event, 'data'=>$data)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($pwg_event_handlers[$event]))
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
|
||||
foreach ($pwg_event_handlers[$event] as $priority => $handlers)
|
||||
{
|
||||
foreach ($handlers as $handler)
|
||||
{
|
||||
$args[0] = $data;
|
||||
|
||||
if (!empty($handler['include_path']))
|
||||
{
|
||||
include_once($handler['include_path']);
|
||||
}
|
||||
|
||||
$data = call_user_func_array($handler['function'], $args);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($pwg_event_handlers['trigger']))
|
||||
{// debugging
|
||||
trigger_notify('trigger',
|
||||
array('type'=>'post_event', 'event'=>$event, 'data'=>$data)
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a notifier event and calls all registered event handlers.
|
||||
* trigger_notify() is only used as a notifier, no modification of data is possible
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param string $event
|
||||
* @param mixed $args,... optional arguments
|
||||
*/
|
||||
function trigger_notify($event)
|
||||
{
|
||||
global $pwg_event_handlers;
|
||||
|
||||
if (isset($pwg_event_handlers['trigger']) and $event!='trigger')
|
||||
{// debugging - avoid recursive calls
|
||||
trigger_notify('trigger',
|
||||
array('type'=>'action', 'event'=>$event, 'data'=>null)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($pwg_event_handlers[$event]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
|
||||
foreach ($pwg_event_handlers[$event] as $priority => $handlers)
|
||||
{
|
||||
foreach ($handlers as $handler)
|
||||
{
|
||||
if (!empty($handler['include_path']))
|
||||
{
|
||||
include_once($handler['include_path']);
|
||||
}
|
||||
|
||||
call_user_func_array($handler['function'], $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves some data with the associated plugin id, data are only available
|
||||
* during script lifetime.
|
||||
* @depracted 2.6
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* @param mixed &$data
|
||||
* @return bool
|
||||
*/
|
||||
function set_plugin_data($plugin_id, &$data)
|
||||
{
|
||||
global $pwg_loaded_plugins;
|
||||
if ( isset($pwg_loaded_plugins[$plugin_id]) )
|
||||
{
|
||||
$pwg_loaded_plugins[$plugin_id]['plugin_data'] = &$data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves plugin data saved previously with set_plugin_data.
|
||||
* @see set_plugin_data()
|
||||
* @depracted 2.6
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* @return mixed
|
||||
*/
|
||||
function &get_plugin_data($plugin_id)
|
||||
{
|
||||
global $pwg_loaded_plugins;
|
||||
if ( isset($pwg_loaded_plugins[$plugin_id]['plugin_data']) )
|
||||
{
|
||||
return $pwg_loaded_plugins[$plugin_id]['plugin_data'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of plugins defined in the database.
|
||||
*
|
||||
* @param string $state optional filter
|
||||
* @param string $id returns only data about given plugin
|
||||
* @return array
|
||||
*/
|
||||
function get_db_plugins($state='', $id='')
|
||||
{
|
||||
$query = '
|
||||
SELECT * FROM '.PLUGINS_TABLE;
|
||||
$clauses = array();
|
||||
if (!empty($state))
|
||||
{
|
||||
$clauses[] = 'state=\''.$state.'\'';
|
||||
}
|
||||
if (!empty($id))
|
||||
{
|
||||
$clauses[] = 'id="'.$id.'"';
|
||||
}
|
||||
if (count($clauses))
|
||||
{
|
||||
$query .= '
|
||||
WHERE '. implode(' AND ', $clauses);
|
||||
}
|
||||
|
||||
return query2array($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a plugin in memory.
|
||||
* It performs autoupdate, includes the main.inc.php file and updates *$pwg_loaded_plugins*.
|
||||
*
|
||||
* @param string $plugin
|
||||
*/
|
||||
function load_plugin($plugin)
|
||||
{
|
||||
$file_name = PHPWG_PLUGINS_PATH.$plugin['id'].'/main.inc.php';
|
||||
if (file_exists($file_name))
|
||||
{
|
||||
autoupdate_plugin($plugin);
|
||||
global $pwg_loaded_plugins;
|
||||
$pwg_loaded_plugins[ $plugin['id'] ] = $plugin;
|
||||
include_once($file_name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs update task of a plugin.
|
||||
* Autoupdate is only performed if the plugin has a maintain.class.php file.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array &$plugin (id, version, state) will be updated if version changes
|
||||
*/
|
||||
function autoupdate_plugin(&$plugin)
|
||||
{
|
||||
// try to find the filesystem version in lines 2 to 10 of main.inc.php
|
||||
$fh = fopen(PHPWG_PLUGINS_PATH.$plugin['id'].'/main.inc.php', 'r');
|
||||
$fs_version = null;
|
||||
$i = -1;
|
||||
|
||||
while (($line = fgets($fh))!==false && $fs_version==null && $i<10)
|
||||
{
|
||||
$i++;
|
||||
if ($i < 2) continue; // first lines are typically "<?php" and "/*"
|
||||
|
||||
if (preg_match('/Version:\\s*([\\w.-]+)/', $line, $matches))
|
||||
{
|
||||
$fs_version = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
fclose($fh);
|
||||
|
||||
// if version is auto (dev) or superior
|
||||
if ($fs_version != null && (
|
||||
$fs_version == 'auto' || $plugin['version'] == 'auto' ||
|
||||
safe_version_compare($plugin['version'], $fs_version, '<')
|
||||
)
|
||||
) {
|
||||
$old_version = $plugin['version'];
|
||||
$new_version = $fs_version;
|
||||
|
||||
$plugin['version'] = $fs_version;
|
||||
|
||||
$maintain_file = PHPWG_PLUGINS_PATH.$plugin['id'].'/maintain.class.php';
|
||||
|
||||
// autoupdate is applicable only to plugins with 2.7 architecture
|
||||
if (file_exists($maintain_file))
|
||||
{
|
||||
global $page;
|
||||
|
||||
// call update method
|
||||
include_once($maintain_file);
|
||||
|
||||
$classname = $plugin['id'].'_maintain';
|
||||
|
||||
// piwigo-videojs and piwigo-openstreetmap unfortunately have a "-" in their folder
|
||||
// name (=plugin_id) and a class name can't have a "-". So we have to replace with a "_"
|
||||
$classname = str_replace('-', '_', $classname);
|
||||
|
||||
$plugin_maintain = new $classname($plugin['id']);
|
||||
$plugin_maintain->update($plugin['version'], $fs_version, $page['errors']);
|
||||
}
|
||||
|
||||
// update database (only on production). We want to avoid registering an "auto" to "auto" update,
|
||||
// which happens for each "version=auto" plugin on each page load.
|
||||
if ($new_version != $old_version)
|
||||
{
|
||||
$query = '
|
||||
UPDATE '. PLUGINS_TABLE .'
|
||||
SET version = "'. $plugin['version'] .'"
|
||||
WHERE id = "'. $plugin['id'] .'"
|
||||
;';
|
||||
pwg_query($query);
|
||||
|
||||
pwg_activity('system', ACTIVITY_SYSTEM_PLUGIN, 'autoupdate', array('plugin_id'=>$plugin['id'], 'from_version'=>$old_version, 'to_version'=>$new_version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the registered plugins.
|
||||
*/
|
||||
function load_plugins()
|
||||
{
|
||||
global $conf, $pwg_loaded_plugins;
|
||||
$pwg_loaded_plugins = array();
|
||||
if ($conf['enable_plugins'])
|
||||
{
|
||||
$plugins = get_db_plugins('active');
|
||||
foreach( $plugins as $plugin)
|
||||
{// include main from a function to avoid using same function context
|
||||
load_plugin($plugin);
|
||||
}
|
||||
trigger_notify('plugins_loaded');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\rate
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Rate a picture by the current user.
|
||||
*
|
||||
* @param int $image_id
|
||||
* @param float $rate
|
||||
* @return array as return by update_rating_score()
|
||||
*/
|
||||
function rate_picture($image_id, $rate)
|
||||
{
|
||||
global $conf, $user;
|
||||
|
||||
if (!isset($rate)
|
||||
or !$conf['rate']
|
||||
or !preg_match('/^[0-9]+$/', $rate)
|
||||
or !in_array($rate, $conf['rate_items']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_anonymous = is_autorize_status(ACCESS_CLASSIC) ? false : true;
|
||||
|
||||
if ($user_anonymous and !$conf['rate_anonymous'])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip_components = explode('.', $_SERVER["REMOTE_ADDR"]);
|
||||
if (count($ip_components) > 3)
|
||||
{
|
||||
array_pop($ip_components);
|
||||
}
|
||||
$anonymous_id = implode ('.', $ip_components);
|
||||
|
||||
if ($user_anonymous)
|
||||
{
|
||||
$save_anonymous_id = pwg_get_cookie_var('anonymous_rater', $anonymous_id);
|
||||
|
||||
if ($anonymous_id != $save_anonymous_id)
|
||||
{ // client has changed his IP adress or he's trying to fool us
|
||||
$query = '
|
||||
SELECT element_id
|
||||
FROM '.RATE_TABLE.'
|
||||
WHERE user_id = '.$user['id'].'
|
||||
AND anonymous_id = \''.$anonymous_id.'\'
|
||||
;';
|
||||
$already_there = array_from_query($query, 'element_id');
|
||||
|
||||
if (count($already_there) > 0)
|
||||
{
|
||||
$query = '
|
||||
DELETE
|
||||
FROM '.RATE_TABLE.'
|
||||
WHERE user_id = '.$user['id'].'
|
||||
AND anonymous_id = \''.$save_anonymous_id.'\'
|
||||
AND element_id IN ('.implode(',', $already_there).')
|
||||
;';
|
||||
pwg_query($query);
|
||||
}
|
||||
|
||||
$query = '
|
||||
UPDATE '.RATE_TABLE.'
|
||||
SET anonymous_id = \'' .$anonymous_id.'\'
|
||||
WHERE user_id = '.$user['id'].'
|
||||
AND anonymous_id = \'' . $save_anonymous_id.'\'
|
||||
;';
|
||||
pwg_query($query);
|
||||
} // end client changed ip
|
||||
|
||||
pwg_set_cookie_var('anonymous_rater', $anonymous_id);
|
||||
} // end anonymous user
|
||||
|
||||
$query = '
|
||||
DELETE
|
||||
FROM '.RATE_TABLE.'
|
||||
WHERE element_id = '.$image_id.'
|
||||
AND user_id = '.$user['id'].'
|
||||
';
|
||||
if ($user_anonymous)
|
||||
{
|
||||
$query.= ' AND anonymous_id = \''.$anonymous_id.'\'';
|
||||
}
|
||||
pwg_query($query);
|
||||
$query = '
|
||||
INSERT
|
||||
INTO '.RATE_TABLE.'
|
||||
(user_id,anonymous_id,element_id,rate,date)
|
||||
VALUES
|
||||
('
|
||||
.$user['id'].','
|
||||
.'\''.$anonymous_id.'\','
|
||||
.$image_id.','
|
||||
.$rate
|
||||
.',NOW())
|
||||
;';
|
||||
pwg_query($query);
|
||||
|
||||
return update_rating_score($image_id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update images.rating_score field.
|
||||
* We use a bayesian average (http://en.wikipedia.org/wiki/Bayesian_average) with
|
||||
* C = average number of rates per item
|
||||
* m = global average rate (all rates)
|
||||
*
|
||||
* @param int|false $element_id if false applies to all
|
||||
* @return array (score, average, count) values are null if $element_id is false
|
||||
*/
|
||||
function update_rating_score($element_id = false)
|
||||
{
|
||||
if ( ($alt_result = trigger_change('update_rating_score', false, $element_id)) !== false)
|
||||
{
|
||||
return $alt_result;
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT element_id,
|
||||
COUNT(rate) AS rcount,
|
||||
SUM(rate) AS rsum
|
||||
FROM '.RATE_TABLE.'
|
||||
GROUP by element_id';
|
||||
|
||||
$all_rates_count = 0;
|
||||
$all_rates_avg = 0;
|
||||
$item_ratecount_avg = 0;
|
||||
$by_item = array();
|
||||
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$all_rates_count += $row['rcount'];
|
||||
$all_rates_avg += $row['rsum'];
|
||||
$by_item[$row['element_id']] = $row;
|
||||
}
|
||||
|
||||
if ($all_rates_count>0)
|
||||
{
|
||||
$all_rates_avg /= $all_rates_count;
|
||||
$item_ratecount_avg = $all_rates_count / count($by_item);
|
||||
}
|
||||
|
||||
$updates = array();
|
||||
foreach ($by_item as $id => $rate_summary )
|
||||
{
|
||||
$score = ( $item_ratecount_avg * $all_rates_avg + $rate_summary['rsum'] ) / ($item_ratecount_avg + $rate_summary['rcount']);
|
||||
$score = round($score,2);
|
||||
if ($id==$element_id)
|
||||
{
|
||||
$return = array(
|
||||
'score' => $score,
|
||||
'average' => round($rate_summary['rsum'] / $rate_summary['rcount'], 2),
|
||||
'count' => $rate_summary['rcount'],
|
||||
);
|
||||
}
|
||||
$updates[] = array( 'id'=>$id, 'rating_score'=>$score );
|
||||
}
|
||||
mass_updates(
|
||||
IMAGES_TABLE,
|
||||
array(
|
||||
'primary' => array('id'),
|
||||
'update' => array('rating_score')
|
||||
),
|
||||
$updates
|
||||
);
|
||||
|
||||
//set to null all items with no rate
|
||||
if ( !isset($by_item[$element_id]) )
|
||||
{
|
||||
$query='
|
||||
SELECT id FROM '.IMAGES_TABLE .'
|
||||
LEFT JOIN '.RATE_TABLE.' ON id=element_id
|
||||
WHERE element_id IS NULL AND rating_score IS NOT NULL';
|
||||
|
||||
$to_update = array_from_query( $query, 'id');
|
||||
|
||||
if ( !empty($to_update) )
|
||||
{
|
||||
$query='
|
||||
UPDATE '.IMAGES_TABLE .'
|
||||
SET rating_score=NULL
|
||||
WHERE id IN (' . implode(',',$to_update) . ')';
|
||||
pwg_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
return isset($return) ? $return : array('score'=>null, 'average'=>null, 'count'=>0 );
|
||||
}
|
||||
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\session
|
||||
*/
|
||||
|
||||
// In PHP 8.4+ calling session_set_save_handler with
|
||||
// two parameters is deprecated. To correct this,
|
||||
// we pass a SessionHandlerInterface instance.
|
||||
// https://github.com/Piwigo/Piwigo/issues/2296
|
||||
// Depending on the PHP version, we include the appropriate
|
||||
// session handler class file.
|
||||
if (version_compare(PHP_VERSION, '8.0.0') < 0)
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'/include/pwgsession_php7.class.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'/include/pwgsession.class.php');
|
||||
}
|
||||
|
||||
if (isset($conf['session_save_handler'])
|
||||
and ($conf['session_save_handler'] == 'db')
|
||||
and defined('PHPWG_INSTALLED'))
|
||||
{
|
||||
session_set_save_handler(new PwgSession());
|
||||
|
||||
if (function_exists('ini_set'))
|
||||
{
|
||||
ini_set('session.use_cookies', $conf['session_use_cookies']);
|
||||
ini_set('session.use_only_cookies', $conf['session_use_only_cookies']);
|
||||
ini_set('session.use_trans_sid', intval($conf['session_use_trans_sid']));
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
}
|
||||
|
||||
session_name($conf['session_name']);
|
||||
session_set_cookie_params(0, cookie_path());
|
||||
register_shutdown_function('session_write_close');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a pseudo random string.
|
||||
* Characters used are a-z A-Z and numerical values.
|
||||
*
|
||||
* @param int $size
|
||||
* @return string
|
||||
*/
|
||||
function generate_key($size)
|
||||
{
|
||||
$bytes = random_bytes($size+10);
|
||||
|
||||
return substr(
|
||||
str_replace(
|
||||
array('+', '/'),
|
||||
'',
|
||||
base64_encode($bytes)
|
||||
),
|
||||
0,
|
||||
$size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, always return true.
|
||||
*
|
||||
* @param string $path
|
||||
* @param sring $name
|
||||
* @return true
|
||||
*/
|
||||
function pwg_session_open($path, $name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, always return true.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
function pwg_session_close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash from current user IP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_remote_addr_session_hash()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!$conf['session_use_ip_address'])
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
if (strpos($_SERVER['REMOTE_ADDR'],':')===false)
|
||||
{//ipv4
|
||||
return vsprintf(
|
||||
"%02X%02X",
|
||||
explode('.',$_SERVER['REMOTE_ADDR'])
|
||||
);
|
||||
}
|
||||
return ''; //ipv6 not yet
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, retrieves data stored in the sessions table.
|
||||
*
|
||||
* @param string $session_id
|
||||
* @return string
|
||||
*/
|
||||
function pwg_session_read($session_id)
|
||||
{
|
||||
$query = '
|
||||
SELECT data
|
||||
FROM '.SESSIONS_TABLE.'
|
||||
WHERE id = \''.get_remote_addr_session_hash().$session_id.'\'
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
if ( ($row = pwg_db_fetch_assoc($result)) )
|
||||
{
|
||||
return $row['data'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, writes data in the sessions table.
|
||||
*
|
||||
* @param string $session_id
|
||||
* @param sring $data
|
||||
* @return true
|
||||
*/
|
||||
function pwg_session_write($session_id, $data)
|
||||
{
|
||||
// when the request is authenticated via api_key (PWG_API_KEY_REQUEST),
|
||||
// you do not want the session to be written to the database (no user session persistence)
|
||||
// this avoids polluting the session table with stateless API accesses
|
||||
if (defined('PWG_API_KEY_REQUEST'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
$query = '
|
||||
REPLACE INTO '.SESSIONS_TABLE.'
|
||||
(id,data,expiration)
|
||||
VALUES(\''.get_remote_addr_session_hash().$session_id.'\',\''.pwg_db_real_escape_string($data).'\',now())
|
||||
;';
|
||||
pwg_query($query);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, deletes data in the sessions table.
|
||||
*
|
||||
* @param string $session_id
|
||||
* @return true
|
||||
*/
|
||||
function pwg_session_destroy($session_id)
|
||||
{
|
||||
$query = '
|
||||
DELETE
|
||||
FROM '.SESSIONS_TABLE.'
|
||||
WHERE id = \''.get_remote_addr_session_hash().$session_id.'\'
|
||||
;';
|
||||
pwg_query($query);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PHP session manager, garbage collector for expired sessions.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
function pwg_session_gc()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$query = '
|
||||
DELETE
|
||||
FROM '.SESSIONS_TABLE.'
|
||||
WHERE '.pwg_db_date_to_ts('NOW()').' - '.pwg_db_date_to_ts('expiration').' > '
|
||||
.$conf['session_length'].'
|
||||
;';
|
||||
pwg_query($query);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistently stores a variable for the current session.
|
||||
*
|
||||
* @param string $var
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
function pwg_set_session_var($var, $value)
|
||||
{
|
||||
if ( !isset($_SESSION) )
|
||||
return false;
|
||||
$_SESSION['pwg_'.$var] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a persistent variable for the current session.
|
||||
*
|
||||
* @param string $var
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
function pwg_get_session_var($var, $default = null)
|
||||
{
|
||||
if (isset( $_SESSION['pwg_'.$var] ) )
|
||||
{
|
||||
return $_SESSION['pwg_'.$var];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a persistent variable for the current session.
|
||||
*
|
||||
* @param string $var
|
||||
* @return bool
|
||||
*/
|
||||
function pwg_unset_session_var($var)
|
||||
{
|
||||
if ( !isset($_SESSION) )
|
||||
return false;
|
||||
unset( $_SESSION['pwg_'.$var] );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete all sessions for a given user (certainly deleted)
|
||||
*
|
||||
* @since 2.8
|
||||
* @param int $user_id
|
||||
* @return null
|
||||
*/
|
||||
function delete_user_sessions($user_id)
|
||||
{
|
||||
$query = '
|
||||
DELETE
|
||||
FROM '.SESSIONS_TABLE.'
|
||||
WHERE data LIKE \'%pwg_uid|i:'.(int)$user_id.';%\'
|
||||
;';
|
||||
pwg_query($query);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\tag
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of available tags for the connected user.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function get_nb_available_tags()
|
||||
{
|
||||
global $user;
|
||||
if (!isset($user['nb_available_tags']))
|
||||
{
|
||||
$user['nb_available_tags'] = count(get_available_tags());
|
||||
single_update(USER_CACHE_TABLE,
|
||||
array('nb_available_tags'=>$user['nb_available_tags']),
|
||||
array('user_id'=>$user['id'])
|
||||
);
|
||||
}
|
||||
return $user['nb_available_tags'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available tags for the connected user (not sorted).
|
||||
* The returned list can be a subset of all existing tags due to permissions,
|
||||
* also tags with no images are not returned.
|
||||
*
|
||||
* @return array [id, name, counter, url_name]
|
||||
*/
|
||||
function get_available_tags($tag_ids=array())
|
||||
{
|
||||
global $persistent_cache, $user;
|
||||
|
||||
$use_persistent_cache = true;
|
||||
|
||||
// we can find top fatter tags among reachable images
|
||||
$query = '
|
||||
SELECT tag_id, COUNT(DISTINCT(it.image_id)) AS counter
|
||||
FROM '.IMAGE_CATEGORY_TABLE.' ic
|
||||
INNER JOIN '.IMAGE_TAG_TABLE.' it
|
||||
ON ic.image_id=it.image_id
|
||||
WHERE 1=1
|
||||
'.get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'ic.image_id'
|
||||
),
|
||||
' AND '
|
||||
);
|
||||
|
||||
if (is_array($tag_ids) and count($tag_ids) > 0)
|
||||
{
|
||||
$use_persistent_cache = false;
|
||||
|
||||
$query .= '
|
||||
AND tag_id IN ('.implode(',', $tag_ids).')
|
||||
';
|
||||
}
|
||||
|
||||
$query .= '
|
||||
GROUP BY tag_id
|
||||
;';
|
||||
|
||||
if ($use_persistent_cache)
|
||||
{
|
||||
$cache_key = $persistent_cache->make_key(__FUNCTION__.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $tag_counters))
|
||||
{
|
||||
$tag_counters = query2array($query, 'tag_id', 'counter');
|
||||
$persistent_cache->set($cache_key, $tag_counters);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$tag_counters = query2array($query, 'tag_id', 'counter');
|
||||
}
|
||||
|
||||
if ( empty($tag_counters) )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.TAGS_TABLE;
|
||||
|
||||
if (count($tag_counters) < 1000)
|
||||
{
|
||||
$query .= '
|
||||
WHERE id IN ('.implode(',', array_keys($tag_counters)).')
|
||||
';
|
||||
}
|
||||
$result = pwg_query($query);
|
||||
|
||||
$tags = array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if (isset($tag_counters[ $row['id'] ]))
|
||||
{
|
||||
$row['counter'] = intval($tag_counters[ $row['id'] ]);
|
||||
$row['name_raw'] = $row['name'];
|
||||
$row['name'] = trigger_change('render_tag_name', $row['name'], $row);
|
||||
$tags[] = $row;
|
||||
}
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tags even associated to no image.
|
||||
*
|
||||
* @return array [id, name, url_name]
|
||||
*/
|
||||
function get_all_tags()
|
||||
{
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.TAGS_TABLE.'
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
$tags = array();
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$row['name_raw'] = $row['name'];
|
||||
$row['name'] = trigger_change('render_tag_name', $row['name'], $row);
|
||||
$tags[] = $row;
|
||||
}
|
||||
|
||||
usort($tags, 'tag_alpha_compare');
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Giving a set of tags with a counter for each one, calculate the display
|
||||
* level of each tag.
|
||||
*
|
||||
* The level of each tag depends on the average count of tags. This
|
||||
* calculation method avoid having very different levels for tags having
|
||||
* nearly the same count when set are small.
|
||||
*
|
||||
* @param array $tags at least [id, counter]
|
||||
* @return array [..., level]
|
||||
*/
|
||||
function add_level_to_tags($tags)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (count($tags) == 0)
|
||||
{
|
||||
return $tags;
|
||||
}
|
||||
|
||||
$total_count = 0;
|
||||
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$total_count+= $tag['counter'];
|
||||
}
|
||||
|
||||
// average count of available tags will determine the level of each tag
|
||||
$tag_average_count = $total_count / count($tags);
|
||||
|
||||
// tag levels threshold calculation: a tag with an average rate must have
|
||||
// the middle level.
|
||||
for ($i = 1; $i < $conf['tags_levels']; $i++)
|
||||
{
|
||||
$threshold_of_level[$i] =
|
||||
2 * $i * $tag_average_count / $conf['tags_levels'];
|
||||
}
|
||||
|
||||
// display sorted tags
|
||||
foreach ($tags as &$tag)
|
||||
{
|
||||
$tag['level'] = 1;
|
||||
|
||||
// based on threshold, determine current tag level
|
||||
for ($i = $conf['tags_levels'] - 1; $i >= 1; $i--)
|
||||
{
|
||||
if ($tag['counter'] > $threshold_of_level[$i])
|
||||
{
|
||||
$tag['level'] = $i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($tag);
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of image ids corresponding to given tags.
|
||||
* AND & OR mode supported.
|
||||
*
|
||||
* @param int[] $tag_ids
|
||||
* @param string mode
|
||||
* @param string $extra_images_where_sql - optionally apply a sql where filter to retrieved images
|
||||
* @param string $order_by - optionally overwrite default photo order
|
||||
* @param bool $user_permissions
|
||||
* @return array
|
||||
*/
|
||||
function get_image_ids_for_tags($tag_ids, $mode='AND', $extra_images_where_sql='', $order_by='', $use_permissions=true)
|
||||
{
|
||||
global $conf;
|
||||
if (empty($tag_ids))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT id
|
||||
FROM '.IMAGES_TABLE.' i ';
|
||||
|
||||
if ($use_permissions)
|
||||
{
|
||||
$query.= '
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' ic ON id=ic.image_id';
|
||||
}
|
||||
|
||||
$query.= '
|
||||
INNER JOIN '.IMAGE_TAG_TABLE.' it ON id=it.image_id
|
||||
WHERE tag_id IN ('.implode(',', $tag_ids).')';
|
||||
|
||||
if ($use_permissions)
|
||||
{
|
||||
$query.= get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
"\n AND"
|
||||
);
|
||||
}
|
||||
|
||||
$query.= (empty($extra_images_where_sql) ? '' : " \nAND (".$extra_images_where_sql.')').'
|
||||
GROUP BY id';
|
||||
|
||||
if ($mode=='AND' and count($tag_ids)>1)
|
||||
{
|
||||
$query .= '
|
||||
HAVING COUNT(DISTINCT tag_id)='.count($tag_ids);
|
||||
}
|
||||
$query .= "\n".(empty($order_by) ? $conf['order_by'] : $order_by);
|
||||
|
||||
return query2array($query, null, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of tags corresponding to given items.
|
||||
*
|
||||
* @param int[] $items
|
||||
* @param int $max_tags
|
||||
* @param int[] $excluded_tag_ids
|
||||
* @return array [id, name, counter, url_name]
|
||||
*/
|
||||
function get_common_tags($items, $max_tags, $excluded_tag_ids=array())
|
||||
{
|
||||
if (empty($items))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
$query = '
|
||||
SELECT t.*, count(*) AS counter
|
||||
FROM '.IMAGE_TAG_TABLE.'
|
||||
INNER JOIN '.TAGS_TABLE.' t ON tag_id = id
|
||||
WHERE image_id IN ('.implode(',', $items).')';
|
||||
if (!empty($excluded_tag_ids))
|
||||
{
|
||||
$query.='
|
||||
AND tag_id NOT IN ('.implode(',', $excluded_tag_ids).')';
|
||||
}
|
||||
$query .='
|
||||
GROUP BY t.id
|
||||
ORDER BY ';
|
||||
if ($max_tags>0)
|
||||
{ // TODO : why ORDER field is in the if ?
|
||||
$query .= 'counter DESC
|
||||
LIMIT '.$max_tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= 'NULL';
|
||||
}
|
||||
|
||||
$result = pwg_query($query);
|
||||
$tags = array();
|
||||
while($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$row['name'] = trigger_change('render_tag_name', $row['name'], $row);
|
||||
$tags[] = $row;
|
||||
}
|
||||
usort($tags, 'tag_alpha_compare');
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of tags corresponding to any of ids, url_names or names.
|
||||
*
|
||||
* @param int[] $ids
|
||||
* @param string[] $url_names
|
||||
* @param string[] $names
|
||||
* @return array [id, name, url_name]
|
||||
*/
|
||||
function find_tags($ids=array(), $url_names=array(), $names=array() )
|
||||
{
|
||||
$where_clauses = array();
|
||||
if (!empty($ids))
|
||||
{
|
||||
$where_clauses[] = 'id IN ('.implode(',', $ids).')';
|
||||
}
|
||||
if (!empty($url_names))
|
||||
{
|
||||
$where_clauses[] =
|
||||
'url_name IN (\''. implode('\', \'', $url_names) .'\')';
|
||||
}
|
||||
if (!empty($names))
|
||||
{
|
||||
$where_clauses[] =
|
||||
'name IN (\''. implode('\', \'', $names) .'\')';
|
||||
}
|
||||
if (empty($where_clauses))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM '.TAGS_TABLE.'
|
||||
WHERE '. implode( '
|
||||
OR ', $where_clauses);
|
||||
|
||||
return query2array($query);
|
||||
}
|
||||
|
||||
function tags_id_compare($a, $b)
|
||||
{
|
||||
return ($a['id'] < $b['id']) ? -1 : 1;
|
||||
}
|
||||
|
||||
function tags_counter_compare($a, $b)
|
||||
{
|
||||
if ($a['counter'] == $b['counter'])
|
||||
{
|
||||
return tags_id_compare($a, $b);
|
||||
}
|
||||
|
||||
return ($a['counter'] < $b['counter']) ? +1 : -1;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,948 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
|
||||
/**
|
||||
* returns a prefix for each url link on displayed page
|
||||
* and return an empty string for current path
|
||||
* @return string
|
||||
*/
|
||||
function get_root_url()
|
||||
{
|
||||
global $page;
|
||||
if ( ($root_url = @$page['root_path']) == null )
|
||||
{// TODO - add HERE the possibility to call PWG functions from external scripts
|
||||
$root_url = PHPWG_ROOT_PATH;
|
||||
if ( strncmp($root_url, './', 2) == 0 )
|
||||
{
|
||||
return substr($root_url, 2);
|
||||
}
|
||||
}
|
||||
return $root_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the absolute url to the root of PWG
|
||||
* @param boolean with_scheme if false - does not add http://toto.com
|
||||
*/
|
||||
function get_absolute_root_url($with_scheme=true)
|
||||
{
|
||||
global $conf;
|
||||
// TODO - add HERE the possibility to call PWG functions from external scripts
|
||||
|
||||
// Support X-Forwarded-Proto header for HTTPS detection in PHP
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'])
|
||||
{
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
}
|
||||
|
||||
$url = '';
|
||||
if ($with_scheme)
|
||||
{
|
||||
$is_https = false;
|
||||
if (isset($_SERVER['HTTPS']) &&
|
||||
((strtolower($_SERVER['HTTPS']) == 'on') or ($_SERVER['HTTPS'] == 1)))
|
||||
{
|
||||
$is_https = true;
|
||||
$url .= 'https://';
|
||||
}
|
||||
else
|
||||
{
|
||||
$url .= 'http://';
|
||||
}
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_HOST']))
|
||||
{
|
||||
$url .= $_SERVER['HTTP_X_FORWARDED_HOST'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$url .= $_SERVER['HTTP_HOST'];
|
||||
|
||||
$url_port = null;
|
||||
|
||||
if ('none' == $conf['url_port'])
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
elseif ('auto' == $conf['url_port'])
|
||||
{
|
||||
if ((!$is_https && $_SERVER['SERVER_PORT'] != 80) || ($is_https && $_SERVER['SERVER_PORT'] != 443))
|
||||
{
|
||||
$url_port = ':'.$_SERVER['SERVER_PORT'];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have a custom port
|
||||
$url_port = ':'.$conf['url_port'];
|
||||
}
|
||||
|
||||
if (!empty($url_port) and strrchr($url, ':') != $url_port)
|
||||
{
|
||||
$url .= $url_port;
|
||||
}
|
||||
}
|
||||
}
|
||||
$url .= cookie_path();
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds one or more _GET style parameters to an url
|
||||
* example: add_url_params('/x', array('a'=>'b')) returns /x?a=b
|
||||
* add_url_params('/x?cat_id=10', array('a'=>'b')) returns /x?cat_id=10&a=b
|
||||
* @param string url
|
||||
* @param array params
|
||||
* @return string
|
||||
*/
|
||||
function add_url_params($url, $params, $arg_separator='&' )
|
||||
{
|
||||
if ( !empty($params) )
|
||||
{
|
||||
assert( is_array($params) );
|
||||
$is_first = true;
|
||||
foreach($params as $param=>$val)
|
||||
{
|
||||
if ($is_first)
|
||||
{
|
||||
$is_first = false;
|
||||
$url .= ( strpos($url, '?')===false ) ? '?' : $arg_separator;
|
||||
}
|
||||
else
|
||||
{
|
||||
$url .= $arg_separator;
|
||||
}
|
||||
$url .= $param;
|
||||
if (isset($val))
|
||||
{
|
||||
$url .= '='.$val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* build an index URL for a specific section
|
||||
*
|
||||
* @param array
|
||||
* @return string
|
||||
*/
|
||||
function make_index_url($params = array())
|
||||
{
|
||||
global $conf;
|
||||
$url = get_root_url().'index';
|
||||
if ($conf['php_extension_in_urls'])
|
||||
{
|
||||
$url .= '.php';
|
||||
}
|
||||
if ($conf['question_mark_in_urls'])
|
||||
{
|
||||
$url .= '?';
|
||||
}
|
||||
|
||||
$url_before_params = $url;
|
||||
|
||||
$url.= make_section_in_url($params);
|
||||
$url = add_well_known_params_in_url($url, $params);
|
||||
|
||||
if ($url == $url_before_params)
|
||||
{
|
||||
$url = get_absolute_root_url( url_is_remote($url) );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* build an index URL with current page parameters, but with redefinitions
|
||||
* and removes.
|
||||
*
|
||||
* duplicate_index_url( array(
|
||||
* 'category' => array('id'=>12, 'name'=>'toto'),
|
||||
* array('start')
|
||||
* ) will create an index URL on the current section (categories), but on
|
||||
* a redefined category and without the start URL parameter.
|
||||
*
|
||||
* @param array redefined keys
|
||||
* @param array removed keys
|
||||
* @return string
|
||||
*/
|
||||
function duplicate_index_url($redefined = array(), $removed = array())
|
||||
{
|
||||
return make_index_url(
|
||||
params_for_duplication($redefined, $removed)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns $page global array with key redefined and key removed
|
||||
*
|
||||
* @param array redefined keys
|
||||
* @param array removed keys
|
||||
* @return array
|
||||
*/
|
||||
function params_for_duplication($redefined, $removed)
|
||||
{
|
||||
global $page;
|
||||
|
||||
$params = $page;
|
||||
|
||||
foreach ($removed as $param_key)
|
||||
{
|
||||
unset($params[$param_key]);
|
||||
}
|
||||
|
||||
foreach ($redefined as $redefined_param => $redefined_value)
|
||||
{
|
||||
$params[$redefined_param] = $redefined_value;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a picture URL with current page parameters, but with redefinitions
|
||||
* and removes. See duplicate_index_url.
|
||||
*
|
||||
* @param array redefined keys
|
||||
* @param array removed keys
|
||||
* @return string
|
||||
*/
|
||||
function duplicate_picture_url($redefined = array(), $removed = array())
|
||||
{
|
||||
return make_picture_url(
|
||||
params_for_duplication($redefined, $removed)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a picture URL on a specific section for a specific picture
|
||||
*
|
||||
* @param array
|
||||
* @return string
|
||||
*/
|
||||
function make_picture_url($params)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$url = get_root_url().'picture';
|
||||
if ($conf['php_extension_in_urls'])
|
||||
{
|
||||
$url .= '.php';
|
||||
}
|
||||
if ($conf['question_mark_in_urls'])
|
||||
{
|
||||
$url .= '?';
|
||||
}
|
||||
$url.= '/';
|
||||
switch ( $conf['picture_url_style'] )
|
||||
{
|
||||
case 'id-file':
|
||||
$url .= $params['image_id'];
|
||||
if ( isset($params['image_file']) )
|
||||
{
|
||||
$url .= '-'.str2url(get_filename_wo_extension($params['image_file']));
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if ( isset($params['image_file']) )
|
||||
{
|
||||
$fname_wo_ext = get_filename_wo_extension($params['image_file']);
|
||||
if ( ord($fname_wo_ext)>ord('9') or !preg_match('/^\d+(-|$)/', $fname_wo_ext) )
|
||||
{
|
||||
$url .= $fname_wo_ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
$url .= $params['image_id'];
|
||||
}
|
||||
if ( !isset($params['category'] ) )
|
||||
{// make urls shorter ...
|
||||
unset( $params['flat'] );
|
||||
}
|
||||
$url .= make_section_in_url($params);
|
||||
$url = add_well_known_params_in_url($url, $params);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
*adds to the url the chronology and start parameters
|
||||
*/
|
||||
function add_well_known_params_in_url($url, $params)
|
||||
{
|
||||
if ( isset($params['chronology_field']) )
|
||||
{
|
||||
$url .= '/'. $params['chronology_field'];
|
||||
$url .= '-'. $params['chronology_style'];
|
||||
if ( isset($params['chronology_view']) )
|
||||
{
|
||||
$url .= '-'. $params['chronology_view'];
|
||||
}
|
||||
if ( !empty($params['chronology_date']) )
|
||||
{
|
||||
$url .= '-'. implode('-', $params['chronology_date'] );
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['flat']))
|
||||
{
|
||||
$url.= '/flat';
|
||||
}
|
||||
|
||||
if (isset($params['start']) and $params['start'] > 0)
|
||||
{
|
||||
$url.= '/start-'.$params['start'];
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the section token of an index or picture URL.
|
||||
*
|
||||
* Depending on section, other parameters are required (see function code
|
||||
* for details)
|
||||
*
|
||||
* @param array
|
||||
* @return string
|
||||
*/
|
||||
function make_section_in_url($params)
|
||||
{
|
||||
global $conf;
|
||||
$section_string = '';
|
||||
$section = @$params['section'];
|
||||
if (!isset($section))
|
||||
{
|
||||
$section_of = array(
|
||||
'category' => 'categories',
|
||||
'tags' => 'tags',
|
||||
'list' => 'list',
|
||||
'search' => 'search',
|
||||
);
|
||||
|
||||
foreach ($section_of as $param => $s)
|
||||
{
|
||||
if (isset($params[$param]))
|
||||
{
|
||||
$section = $s;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($section))
|
||||
{
|
||||
$section = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
switch($section)
|
||||
{
|
||||
case 'categories' :
|
||||
{
|
||||
if (!isset($params['category']))
|
||||
{
|
||||
$section_string.= '/categories';
|
||||
}
|
||||
else
|
||||
{
|
||||
isset($params['category']['name']) or trigger_error(
|
||||
'make_section_in_url category name not set', E_USER_WARNING
|
||||
);
|
||||
|
||||
array_key_exists('permalink', $params['category']) or trigger_error(
|
||||
'make_section_in_url category permalink not set', E_USER_WARNING
|
||||
);
|
||||
|
||||
$section_string.= '/category/';
|
||||
if ( empty($params['category']['permalink']) )
|
||||
{
|
||||
$section_string.= $params['category']['id'];
|
||||
if ( $conf['category_url_style']=='id-name' )
|
||||
{
|
||||
$section_string.= '-'.str2url($params['category']['name']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$section_string.= $params['category']['permalink'];
|
||||
}
|
||||
|
||||
if (isset($params['combined_categories']))
|
||||
{
|
||||
foreach ($params['combined_categories'] as $category)
|
||||
{
|
||||
$section_string.= '/';
|
||||
|
||||
if ( empty($category['permalink']) )
|
||||
{
|
||||
$section_string.= $category['id'];
|
||||
if ( $conf['category_url_style']=='id-name' )
|
||||
{
|
||||
$section_string.= '-'.str2url($category['name']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$section_string.= $category['permalink'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'tags' :
|
||||
{
|
||||
$section_string.= '/tags';
|
||||
|
||||
foreach ($params['tags'] as $tag)
|
||||
{
|
||||
switch ( $conf['tag_url_style'] )
|
||||
{
|
||||
case 'id':
|
||||
$section_string.= '/'.$tag['id'];
|
||||
break;
|
||||
case 'tag':
|
||||
if (isset($tag['url_name']))
|
||||
{
|
||||
$section_string.= '/'.$tag['url_name'];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
$section_string.= '/'.$tag['id'];
|
||||
if (isset($tag['url_name']))
|
||||
{
|
||||
$section_string.= '-'.$tag['url_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'search' :
|
||||
{
|
||||
$section_string.= '/search/'.$params['search'];
|
||||
break;
|
||||
}
|
||||
case 'list' :
|
||||
{
|
||||
$section_string.= '/list/'.implode(',', $params['list']);
|
||||
break;
|
||||
}
|
||||
case 'none' :
|
||||
{
|
||||
break;
|
||||
}
|
||||
default :
|
||||
{
|
||||
$section_string.= '/'.$section;
|
||||
}
|
||||
}
|
||||
|
||||
return $section_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* the reverse of make_section_in_url
|
||||
* returns the 'section' (categories/tags/...) and the data associated with it
|
||||
*
|
||||
* Depending on section, other parameters are returned (category/tags/list/...)
|
||||
*
|
||||
* @param array of url tokens to parse
|
||||
* @param int the index in the array of url tokens; in/out
|
||||
* @return array
|
||||
*/
|
||||
function parse_section_url( $tokens, &$next_token)
|
||||
{
|
||||
$page=array();
|
||||
if (isset($tokens[$next_token]) and strncmp($tokens[$next_token], 'categor', 7)==0 )
|
||||
{
|
||||
$page['section'] = 'categories';
|
||||
$next_token++;
|
||||
|
||||
$i = $next_token;
|
||||
$loop_counter = 0;
|
||||
|
||||
while (isset($tokens[$next_token]))
|
||||
{
|
||||
if ($loop_counter++ > count($tokens)+10){die('infinite loop?');}
|
||||
|
||||
if (
|
||||
strpos($tokens[$next_token], 'created-')===0
|
||||
or strpos($tokens[$next_token], 'posted-')===0
|
||||
or strpos($tokens[$next_token], 'start-')===0
|
||||
or strpos($tokens[$next_token], 'startcat-')===0
|
||||
or 'flat' == $tokens[$next_token]
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d+)(?:-(.+))?$/', $tokens[$next_token], $matches))
|
||||
{
|
||||
if ( isset($matches[2]) )
|
||||
$page['hit_by']['cat_url_name'] = $matches[2];
|
||||
|
||||
if (!isset($page['category']))
|
||||
{
|
||||
$page['category'] = $matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['combined_categories'][] = $matches[1];
|
||||
}
|
||||
$next_token++;
|
||||
}
|
||||
else
|
||||
{// try a permalink
|
||||
$maybe_permalinks = array();
|
||||
$current_token = $next_token;
|
||||
while ( isset($tokens[$current_token])
|
||||
and strpos($tokens[$current_token], 'created-')!==0
|
||||
and strpos($tokens[$current_token], 'posted-')!==0
|
||||
and strpos($tokens[$next_token], 'start-')!==0
|
||||
and strpos($tokens[$next_token], 'startcat-')!==0
|
||||
and $tokens[$current_token] != 'flat')
|
||||
{
|
||||
if (empty($maybe_permalinks))
|
||||
{
|
||||
$maybe_permalinks[] = $tokens[$current_token];
|
||||
}
|
||||
else
|
||||
{
|
||||
$maybe_permalinks[] =
|
||||
$maybe_permalinks[count($maybe_permalinks)-1]
|
||||
. '/' . $tokens[$current_token];
|
||||
}
|
||||
$current_token++;
|
||||
}
|
||||
|
||||
if ( count($maybe_permalinks) )
|
||||
{
|
||||
$cat_id = get_cat_id_from_permalinks($maybe_permalinks, $perma_index);
|
||||
if ( isset($cat_id) )
|
||||
{
|
||||
$next_token += $perma_index+1;
|
||||
|
||||
if (!isset($page['category']))
|
||||
{
|
||||
$page['category'] = $cat_id;
|
||||
$page['hit_by']['cat_permalink'] = $maybe_permalinks[$perma_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['combined_categories'][] = $cat_id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
page_not_found(l10n('Permalink for album not found'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($page['category']))
|
||||
{
|
||||
$result = get_cat_info($page['category']);
|
||||
if (empty($result))
|
||||
{
|
||||
page_not_found(l10n('Requested album does not exist'));
|
||||
}
|
||||
$page['category']=$result;
|
||||
}
|
||||
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$combined_categories = array();
|
||||
|
||||
foreach ($page['combined_categories'] as $cat_id)
|
||||
{
|
||||
$result = get_cat_info($cat_id);
|
||||
if (empty($result))
|
||||
{
|
||||
page_not_found(l10n('Requested album does not exist'));
|
||||
}
|
||||
|
||||
$combined_categories[] = $result;
|
||||
}
|
||||
|
||||
$page['combined_categories'] = $combined_categories;
|
||||
}
|
||||
}
|
||||
elseif ( 'tags' == @$tokens[$next_token] )
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$page['section'] = 'tags';
|
||||
$page['tags'] = array();
|
||||
|
||||
$next_token++;
|
||||
$i = $next_token;
|
||||
|
||||
$requested_tag_ids = array();
|
||||
$requested_tag_url_names = array();
|
||||
|
||||
while (isset($tokens[$i]))
|
||||
{
|
||||
if (strpos($tokens[$i], 'created-')===0
|
||||
or strpos($tokens[$i], 'posted-')===0
|
||||
or strpos($tokens[$i], 'start-')===0 )
|
||||
break;
|
||||
|
||||
if ( $conf['tag_url_style'] != 'tag' and preg_match('/^(\d+)(?:-(.*)|)$/', $tokens[$i], $matches) )
|
||||
{
|
||||
$requested_tag_ids[] = $matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$requested_tag_url_names[] = $tokens[$i];
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$next_token = $i;
|
||||
|
||||
if ( empty($requested_tag_ids) && empty($requested_tag_url_names) )
|
||||
{
|
||||
bad_request('at least one tag required');
|
||||
}
|
||||
|
||||
$page['tags'] = find_tags($requested_tag_ids, $requested_tag_url_names);
|
||||
if ( empty($page['tags']) )
|
||||
{
|
||||
page_not_found(l10n('Requested tag does not exist'), get_root_url().'tags.php' );
|
||||
}
|
||||
}
|
||||
elseif ( 'favorites' == @$tokens[$next_token] )
|
||||
{
|
||||
$page['section'] = 'favorites';
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('most_visited' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'most_visited';
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('best_rated' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'best_rated';
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('recent_pics' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'recent_pics';
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('recent_cats' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'recent_cats';
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('search' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'search';
|
||||
$next_token++;
|
||||
|
||||
preg_match('/^(psk-\d{8}-[a-zA-Z0-9]{10})$/', @$tokens[$next_token], $matches);
|
||||
if (!isset($matches[1]))
|
||||
{
|
||||
preg_match('/(\d+)/', @$tokens[$next_token], $matches);
|
||||
if (!isset($matches[1]))
|
||||
{
|
||||
bad_request('search identifier is missing');
|
||||
}
|
||||
}
|
||||
$page['search'] = $matches[1];
|
||||
$next_token++;
|
||||
}
|
||||
elseif ('list' == @$tokens[$next_token])
|
||||
{
|
||||
$page['section'] = 'list';
|
||||
$next_token++;
|
||||
|
||||
$page['list'] = array();
|
||||
|
||||
// No pictures
|
||||
if (empty($tokens[$next_token]))
|
||||
{
|
||||
// Add dummy element list
|
||||
$page['list'][] = -1;
|
||||
}
|
||||
// With pictures list
|
||||
else
|
||||
{
|
||||
if (!preg_match('/^\d+(,\d+)*$/', $tokens[$next_token]))
|
||||
{
|
||||
bad_request('wrong format on list GET parameter');
|
||||
}
|
||||
foreach (explode(',', $tokens[$next_token]) as $image_id)
|
||||
{
|
||||
$page['list'][] = $image_id;
|
||||
}
|
||||
}
|
||||
$next_token++;
|
||||
}
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* the reverse of add_well_known_params_in_url
|
||||
* parses start, flat and chronology from url tokens
|
||||
*/
|
||||
function parse_well_known_params_url($tokens, &$i)
|
||||
{
|
||||
$page = array();
|
||||
while (isset($tokens[$i]))
|
||||
{
|
||||
if ( 'flat' == $tokens[$i] )
|
||||
{
|
||||
// indicate a special list of images
|
||||
$page['flat'] = true;
|
||||
}
|
||||
elseif (strpos($tokens[$i], 'created-')===0 or strpos($tokens[$i], 'posted-')===0)
|
||||
{
|
||||
$chronology_tokens = explode('-', $tokens[$i] );
|
||||
|
||||
$page['chronology_field'] = $chronology_tokens[0];
|
||||
|
||||
array_shift($chronology_tokens);
|
||||
$page['chronology_style'] = $chronology_tokens[0];
|
||||
|
||||
if (!in_array($page['chronology_style'], array('monthly', 'weekly')))
|
||||
{
|
||||
fatal_error('bad chronology field (style)');
|
||||
}
|
||||
|
||||
array_shift($chronology_tokens);
|
||||
if ( count($chronology_tokens)>0 )
|
||||
{
|
||||
if ('list'==$chronology_tokens[0] or
|
||||
'calendar'==$chronology_tokens[0])
|
||||
{
|
||||
$page['chronology_view'] = $chronology_tokens[0];
|
||||
array_shift($chronology_tokens);
|
||||
}
|
||||
$page['chronology_date'] = $chronology_tokens;
|
||||
|
||||
foreach ($page['chronology_date'] as $date_token)
|
||||
{
|
||||
// each date part must be an integer (number of the year, number of the month, number of the week or number of the day)
|
||||
if (!preg_match('/^(\d+|any)$/', $date_token))
|
||||
{
|
||||
fatal_error('bad chronology field (date)');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (preg_match('/^start-(\d+)/', $tokens[$i], $matches))
|
||||
{
|
||||
$page['start'] = $matches[1];
|
||||
}
|
||||
elseif (preg_match('/^startcat-(\d+)/', $tokens[$i], $matches))
|
||||
{
|
||||
$page['startcat'] = $matches[1];
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $page;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param id image id
|
||||
* @param what_part string one of 'e' (element), 'r' (representative)
|
||||
*/
|
||||
function get_action_url($id, $what_part, $download)
|
||||
{
|
||||
$params = array(
|
||||
'id' => $id,
|
||||
'part' => $what_part,
|
||||
);
|
||||
if ($download)
|
||||
{
|
||||
$params['download'] = null;
|
||||
}
|
||||
|
||||
return add_url_params(get_root_url().'action.php', $params);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param element_info array containing element information from db;
|
||||
* at least 'id', 'path' should be present
|
||||
*/
|
||||
function get_element_url($element_info)
|
||||
{
|
||||
$url = $element_info['path'];
|
||||
if ( !url_is_remote($url) )
|
||||
{
|
||||
$url = embellish_url(get_root_url().$url);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicate to build url with full path
|
||||
*
|
||||
* @param null
|
||||
* @return null
|
||||
*/
|
||||
function set_make_full_url()
|
||||
{
|
||||
global $page;
|
||||
|
||||
if (!isset($page['save_root_path']))
|
||||
{
|
||||
if (isset($page['root_path']))
|
||||
{
|
||||
$page['save_root_path']['path'] = $page['root_path'];
|
||||
}
|
||||
$page['save_root_path']['count'] = 1;
|
||||
$page['root_path'] = get_absolute_root_url();
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['save_root_path']['count'] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore old parameter to build url with full path
|
||||
*
|
||||
* @param null
|
||||
* @return null
|
||||
*/
|
||||
function unset_make_full_url()
|
||||
{
|
||||
global $page;
|
||||
|
||||
if (isset($page['save_root_path']))
|
||||
{
|
||||
if ($page['save_root_path']['count'] == 1)
|
||||
{
|
||||
if (isset($page['save_root_path']['path']))
|
||||
{
|
||||
$page['root_path'] = $page['save_root_path']['path'];
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($page['root_path']);
|
||||
}
|
||||
unset($page['save_root_path']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['save_root_path']['count'] -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embellish the url argument
|
||||
*
|
||||
* @param $url
|
||||
* @return $url embellished
|
||||
*/
|
||||
function embellish_url($url)
|
||||
{
|
||||
$url = str_replace('/./', '/', $url);
|
||||
while ( ($dotdot = strpos($url, '/../', 1) ) !== false )
|
||||
{
|
||||
$before = strrpos($url, '/', -(strlen($url)-$dotdot+1) );
|
||||
if ($before !== false)
|
||||
{
|
||||
$url = substr_replace($url, '', $before, $dotdot-$before+3);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'home page' of this gallery
|
||||
*/
|
||||
function get_gallery_home_url()
|
||||
{
|
||||
global $conf;
|
||||
if (!empty($conf['gallery_url']))
|
||||
{
|
||||
if (url_is_remote($conf['gallery_url']) or $conf['gallery_url'][0]=='/' )
|
||||
{
|
||||
return $conf['gallery_url'];
|
||||
}
|
||||
return get_root_url().$conf['gallery_url'];
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_index_url();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns $_SERVER['QUERY_STRING'] whithout keys given in parameters
|
||||
*
|
||||
* @param string[] $rejects
|
||||
* @param boolean $escape escape *&* to *&*
|
||||
* @returns string
|
||||
*/
|
||||
function get_query_string_diff($rejects=array(), $escape=true)
|
||||
{
|
||||
if (empty($_SERVER['QUERY_STRING']))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
parse_str($_SERVER['QUERY_STRING'], $vars);
|
||||
|
||||
$vars = array_diff_key($vars, array_flip($rejects));
|
||||
|
||||
return '?' . http_build_query($vars, '', $escape ? '&' : '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the url is absolute (begins with http)
|
||||
*
|
||||
* @param string $url
|
||||
* @returns boolean
|
||||
*/
|
||||
function url_is_remote($url)
|
||||
{
|
||||
if ( strncmp($url, 'http://', 7)==0
|
||||
or strncmp($url, 'https://', 8)==0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* List favorite image_ids of the current user.
|
||||
* @since 13
|
||||
*/
|
||||
function get_user_favorites()
|
||||
{
|
||||
global $user;
|
||||
|
||||
if (is_a_guest())
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
image_id,
|
||||
1 as fake_value
|
||||
FROM '.FAVORITES_TABLE.'
|
||||
WHERE user_id = '.$user['id'].'
|
||||
';
|
||||
|
||||
return query2array($query, 'image_id', 'fake_value');
|
||||
}
|
||||
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
// Recursive call
|
||||
$url = '../';
|
||||
header( 'Request-URI: '.$url );
|
||||
header( 'Content-Location: '.$url );
|
||||
header( 'Location: '.$url );
|
||||
exit();
|
||||
?>
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
class Inflector_en
|
||||
{
|
||||
private $exceptions;
|
||||
private $pluralizers;
|
||||
private $singularizers;
|
||||
private $er2ing;
|
||||
private $ing2er;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$tmp = array ('octopus' => 'octopuses',
|
||||
'virus' => 'viruses',
|
||||
'person' => 'people',
|
||||
'man' => 'men',
|
||||
'woman' => 'women',
|
||||
'child' => 'children',
|
||||
'move' => 'moves',
|
||||
'mouse' => 'mice',
|
||||
'ox' => 'oxen',
|
||||
'zombie' => 'zombies', // pl->sg exc.
|
||||
'serie' => 'series', // pl->sg exc.
|
||||
'movie' => 'movies', // pl->sg exc.
|
||||
);
|
||||
|
||||
$this->exceptions = $tmp;
|
||||
foreach ($tmp as $k => $v)
|
||||
$this->exceptions[$v] = $k;
|
||||
|
||||
foreach ( explode(' ', 'new news advice art coal baggage butter clothing cotton currency deer energy equipment experience fish flour food furniture gas homework impatience information jeans knowledge leather love luggage money oil patience police polish progress research rice series sheep silk soap species sugar talent toothpaste travel vinegar weather wood wool work')
|
||||
as $v)
|
||||
{
|
||||
$this->exceptions[$v] = 0;
|
||||
}
|
||||
|
||||
$this->pluralizers = array_reverse(array( '/$/' => 's',
|
||||
'/s$/' => 's',
|
||||
'/^(ax|test)is$/' => '\1es',
|
||||
'/(alias|status)$/' => '\1es',
|
||||
'/(bu)s$/' => '\1ses',
|
||||
'/(buffal|tomat)o$/' => '\1oes',
|
||||
'/([ti])um$/' => '\1a',
|
||||
'/([ti])a$/' => '\1a',
|
||||
'/sis$/' => 'ses',
|
||||
'/(?:([^f])fe|([lr])f)$/' => '\1\2ves',
|
||||
'/(hive)$/' => '\1s',
|
||||
'/([^aeiouy]|qu)y$/' => '\1ies',
|
||||
'/(x|ch|ss|sh)$/' => '\1es',
|
||||
'/(matr|vert|ind)(?:ix|ex)$/' => '\1ices',
|
||||
'/(quiz)$/' => '\1zes',
|
||||
));
|
||||
|
||||
$this->singularizers = array_reverse(array(
|
||||
'/s$/' => '',
|
||||
'/(ss)$/' => '\1',
|
||||
'/([ti])a$/' => '\1um',
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/' => '\1sis',
|
||||
'/(^analy)(sis|ses)$/' => '\1sis',
|
||||
'/([^f])ves$/' => '\1fe',
|
||||
'/(hive)s$/' => '\1',
|
||||
'/(tive)s$/' => '\1',
|
||||
'/([lr])ves$/' => '\1f',
|
||||
'/([^aeiouy]|qu)ies$/' => '\1y',
|
||||
'/(x|ch|ss|sh)es$/' => '\1',
|
||||
'/(bus)(es)?$/' => '\1',
|
||||
'/(o)es$/' => '\1',
|
||||
'/(shoe)s$/' => '\1',
|
||||
'/(cris|test)(is|es)$/' => '\1is',
|
||||
'/^(a)x[ie]s$/' => '\1xis',
|
||||
'/(alias|status)(es)?$/' => '\1',
|
||||
'/(vert|ind)ices$/' => '\1ex',
|
||||
'/(matr)ices$/' => '\1ix',
|
||||
'/(quiz)zes$/' => '\1',
|
||||
'/(database)s$/' => '\1',
|
||||
));
|
||||
|
||||
$this->er2ing = array_reverse(array(
|
||||
'/ers?$/' => 'ing',
|
||||
'/(be|draw|liv)ers?$/' => '\0'
|
||||
));
|
||||
|
||||
$this->ing2er = array_reverse(array(
|
||||
'/ing$/' => 'er',
|
||||
'/(snow|rain)ing$/' => '\1',
|
||||
'/(th|hous|dur|spr|wedd)ing$/' => '\0',
|
||||
'/(liv|draw)ing$/' => '\0'
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
function get_variants($word)
|
||||
{
|
||||
$res = array();
|
||||
|
||||
$lword = strtolower($word);
|
||||
|
||||
$rc = @$this->exceptions[$lword];
|
||||
if ( isset($rc) )
|
||||
{
|
||||
if (!empty($rc))
|
||||
$res[] = $rc;
|
||||
return $res;
|
||||
}
|
||||
|
||||
self::run($this->pluralizers, $word, $res);
|
||||
self::run($this->singularizers, $word, $res);
|
||||
if (strlen($word)>4)
|
||||
{
|
||||
self::run($this->er2ing, $word, $res);
|
||||
}
|
||||
if (strlen($word)>5)
|
||||
{
|
||||
$rc = self::run($this->ing2er, $word, $res);
|
||||
if ($rc !== false)
|
||||
{
|
||||
self::run($this->pluralizers, $rc, $res);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
private static function run($rules, $word, &$res)
|
||||
{
|
||||
foreach ($rules as $rule => $replacement)
|
||||
{
|
||||
$rc = preg_replace($rule.'i', $replacement, $word, -1, $count);
|
||||
if ($count)
|
||||
{
|
||||
if ($rc !== $word)
|
||||
{
|
||||
$res[] = $rc;
|
||||
return $rc;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
class Inflector_fr
|
||||
{
|
||||
private $exceptions;
|
||||
private $pluralizers;
|
||||
private $singularizers;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$tmp = array ('monsieur' => 'messieurs',
|
||||
'madame' => 'mesdames',
|
||||
'mademoiselle' => 'mesdemoiselles',
|
||||
);
|
||||
|
||||
$this->exceptions = $tmp;
|
||||
foreach ($tmp as $k => $v)
|
||||
$this->exceptions[$v] = $k;
|
||||
|
||||
$this->pluralizers = array_reverse(array( '/$/' => 's',
|
||||
'/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)$/' => '\1x',
|
||||
'/(bleu|émeu|landau|lieu|pneu|sarrau)$/' => '\1s',
|
||||
'/al$/' => 'aux',
|
||||
'/ail$/' => 'ails',
|
||||
'/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/' => '\1aux',
|
||||
'/(s|x|z)$/' => '\1',
|
||||
));
|
||||
|
||||
$this->singularizers = array_reverse(array(
|
||||
'/s$/' => '',
|
||||
'/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/' => '\1',
|
||||
'/(journ|chev)aux$/' => '\1al',
|
||||
'/ails$/' => 'ail',
|
||||
'/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/' => '\1ail',
|
||||
));
|
||||
}
|
||||
|
||||
function get_variants($word)
|
||||
{
|
||||
$res = array();
|
||||
|
||||
$word = strtolower($word);
|
||||
|
||||
$rc = @$this->exceptions[$word];
|
||||
if ( isset($rc) )
|
||||
{
|
||||
if (!empty($rc))
|
||||
$res[] = $rc;
|
||||
return $res;
|
||||
}
|
||||
|
||||
foreach ($this->pluralizers as $rule => $replacement)
|
||||
{
|
||||
$rc = preg_replace($rule, $replacement, $word, -1, $count);
|
||||
if ($count)
|
||||
{
|
||||
$res[] = $rc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->singularizers as $rule => $replacement)
|
||||
{
|
||||
$rc = preg_replace($rule, $replacement, $word, -1, $count);
|
||||
if ($count)
|
||||
{
|
||||
$res[] = $rc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the JShrink package.
|
||||
*
|
||||
* (c) Robert Hafner <tedivm@tedivm.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JShrink
|
||||
*
|
||||
*
|
||||
* @package JShrink
|
||||
* @author Robert Hafner <tedivm@tedivm.com>
|
||||
*/
|
||||
|
||||
namespace JShrink;
|
||||
|
||||
/**
|
||||
* Minifier
|
||||
*
|
||||
* Usage - Minifier::minify($js);
|
||||
* Usage - Minifier::minify($js, $options);
|
||||
* Usage - Minifier::minify($js, array('flaggedComments' => false));
|
||||
*
|
||||
* @package JShrink
|
||||
* @author Robert Hafner <tedivm@tedivm.com>
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*/
|
||||
class Minifier
|
||||
{
|
||||
/**
|
||||
* The input javascript to be minified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Length of input javascript.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $len = 0;
|
||||
|
||||
/**
|
||||
* The location of the character (in the input string) that is next to be
|
||||
* processed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $index = 0;
|
||||
|
||||
/**
|
||||
* The first of the characters currently being looked at.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $a = '';
|
||||
|
||||
/**
|
||||
* The next character being looked at (after a);
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $b = '';
|
||||
|
||||
/**
|
||||
* This character is only active when certain look ahead actions take place.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $c;
|
||||
|
||||
/**
|
||||
* Contains the options for the current minification process.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* These characters are used to define strings.
|
||||
*/
|
||||
protected $stringDelimiters = ['\'' => true, '"' => true, '`' => true];
|
||||
|
||||
/**
|
||||
* Contains the default options for minification. This array is merged with
|
||||
* the one passed in by the user to create the request specific set of
|
||||
* options (stored in the $options attribute).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $defaultOptions = ['flaggedComments' => true];
|
||||
|
||||
/**
|
||||
* Contains lock ids which are used to replace certain code patterns and
|
||||
* prevent them from being minified
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $locks = [];
|
||||
|
||||
/**
|
||||
* Takes a string containing javascript and removes unneeded characters in
|
||||
* order to shrink the code without altering it's functionality.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
* @throws \Exception
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function minify($js, $options = [])
|
||||
{
|
||||
try {
|
||||
ob_start();
|
||||
|
||||
$jshrink = new Minifier();
|
||||
$js = $jshrink->lock($js);
|
||||
$jshrink->minifyDirectToOutput($js, $options);
|
||||
|
||||
// Sometimes there's a leading new line, so we trim that out here.
|
||||
$js = ltrim(ob_get_clean());
|
||||
$js = $jshrink->unlock($js);
|
||||
unset($jshrink);
|
||||
|
||||
return $js;
|
||||
} catch (\Exception $e) {
|
||||
if (isset($jshrink)) {
|
||||
// Since the breakdownScript function probably wasn't finished
|
||||
// we clean it out before discarding it.
|
||||
$jshrink->clean();
|
||||
unset($jshrink);
|
||||
}
|
||||
|
||||
// without this call things get weird, with partially outputted js.
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a javascript string and outputs only the required characters,
|
||||
* stripping out all unneeded characters.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*/
|
||||
protected function minifyDirectToOutput($js, $options)
|
||||
{
|
||||
$this->initialize($js, $options);
|
||||
$this->loop();
|
||||
$this->clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes internal variables, normalizes new lines,
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*/
|
||||
protected function initialize($js, $options)
|
||||
{
|
||||
$this->options = array_merge(static::$defaultOptions, $options);
|
||||
$this->input = str_replace(["\r\n", '/**/', "\r"], ["\n", "", "\n"], $js);
|
||||
|
||||
// We add a newline to the end of the script to make it easier to deal
|
||||
// with comments at the bottom of the script- this prevents the unclosed
|
||||
// comment error that can otherwise occur.
|
||||
$this->input .= PHP_EOL;
|
||||
|
||||
// save input length to skip calculation every time
|
||||
$this->len = strlen($this->input);
|
||||
|
||||
// Populate "a" with a new line, "b" with the first character, before
|
||||
// entering the loop
|
||||
$this->a = "\n";
|
||||
$this->b = $this->getReal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Characters that can't stand alone preserve the newline.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $noNewLineCharacters = [
|
||||
'(' => true,
|
||||
'-' => true,
|
||||
'+' => true,
|
||||
'[' => true,
|
||||
'@' => true];
|
||||
|
||||
/**
|
||||
* The primary action occurs here. This function loops through the input string,
|
||||
* outputting anything that's relevant and discarding anything that is not.
|
||||
*/
|
||||
protected function loop()
|
||||
{
|
||||
while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
|
||||
switch ($this->a) {
|
||||
// new lines
|
||||
case "\n":
|
||||
// if the next line is something that can't stand alone preserve the newline
|
||||
if ($this->b !== false && isset($this->noNewLineCharacters[$this->b])) {
|
||||
echo $this->a;
|
||||
$this->saveString();
|
||||
break;
|
||||
}
|
||||
|
||||
// if B is a space we skip the rest of the switch block and go down to the
|
||||
// string/regex check below, resetting $this->b with getReal
|
||||
if ($this->b === ' ') {
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise we treat the newline like a space
|
||||
|
||||
// no break
|
||||
case ' ':
|
||||
if (static::isAlphaNumeric($this->b)) {
|
||||
echo $this->a;
|
||||
}
|
||||
|
||||
$this->saveString();
|
||||
break;
|
||||
|
||||
default:
|
||||
switch ($this->b) {
|
||||
case "\n":
|
||||
if (strpos('}])+-"\'', $this->a) !== false) {
|
||||
echo $this->a;
|
||||
$this->saveString();
|
||||
break;
|
||||
} else {
|
||||
if (static::isAlphaNumeric($this->a)) {
|
||||
echo $this->a;
|
||||
$this->saveString();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
if (!static::isAlphaNumeric($this->a)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// no break
|
||||
default:
|
||||
// check for some regex that breaks stuff
|
||||
if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) {
|
||||
$this->saveRegex();
|
||||
continue 3;
|
||||
}
|
||||
|
||||
echo $this->a;
|
||||
$this->saveString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do reg check of doom
|
||||
$this->b = $this->getReal();
|
||||
|
||||
if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
|
||||
$this->saveRegex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets attributes that do not need to be stored between requests so that
|
||||
* the next request is ready to go. Another reason for this is to make sure
|
||||
* the variables are cleared and are not taking up memory.
|
||||
*/
|
||||
protected function clean()
|
||||
{
|
||||
unset($this->input);
|
||||
$this->len = 0;
|
||||
$this->index = 0;
|
||||
$this->a = $this->b = '';
|
||||
unset($this->c);
|
||||
unset($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next string for processing based off of the current index.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getChar()
|
||||
{
|
||||
// Check to see if we had anything in the look ahead buffer and use that.
|
||||
if (isset($this->c)) {
|
||||
$char = $this->c;
|
||||
unset($this->c);
|
||||
} else {
|
||||
// Otherwise we start pulling from the input.
|
||||
$char = $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
|
||||
// If the next character doesn't exist return false.
|
||||
if (isset($char) && $char === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise increment the pointer and use this char.
|
||||
$this->index++;
|
||||
}
|
||||
|
||||
// Normalize all whitespace except for the newline character into a
|
||||
// standard space.
|
||||
if ($char !== "\n" && $char < "\x20") {
|
||||
return ' ';
|
||||
}
|
||||
|
||||
return $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gets the next "real" character. It is essentially a wrapper
|
||||
* around the getChar function that skips comments. This has significant
|
||||
* performance benefits as the skipping is done using native functions (ie,
|
||||
* c code) rather than in script php.
|
||||
*
|
||||
*
|
||||
* @return string Next 'real' character to be processed.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getReal()
|
||||
{
|
||||
$startIndex = $this->index;
|
||||
$char = $this->getChar();
|
||||
|
||||
// Check to see if we're potentially in a comment
|
||||
if ($char !== '/') {
|
||||
return $char;
|
||||
}
|
||||
|
||||
$this->c = $this->getChar();
|
||||
|
||||
if ($this->c === '/') {
|
||||
$this->processOneLineComments($startIndex);
|
||||
|
||||
return $this->getReal();
|
||||
} elseif ($this->c === '*') {
|
||||
$this->processMultiLineComments($startIndex);
|
||||
|
||||
return $this->getReal();
|
||||
}
|
||||
|
||||
return $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed one line comments, with the exception of some very specific types of
|
||||
* conditional comments.
|
||||
*
|
||||
* @param int $startIndex The index point where "getReal" function started
|
||||
* @return void
|
||||
*/
|
||||
protected function processOneLineComments($startIndex)
|
||||
{
|
||||
$thirdCommentString = $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
|
||||
// kill rest of line
|
||||
$this->getNext("\n");
|
||||
|
||||
unset($this->c);
|
||||
|
||||
if ($thirdCommentString == '@') {
|
||||
$endPoint = $this->index - $startIndex;
|
||||
$this->c = "\n" . substr($this->input, $startIndex, $endPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips multiline comments where appropriate, and includes them where needed.
|
||||
* Conditional comments and "license" style blocks are preserved.
|
||||
*
|
||||
* @param int $startIndex The index point where "getReal" function started
|
||||
* @return void
|
||||
* @throws \RuntimeException Unclosed comments will throw an error
|
||||
*/
|
||||
protected function processMultiLineComments($startIndex)
|
||||
{
|
||||
$this->getChar(); // current C
|
||||
$thirdCommentString = $this->getChar();
|
||||
|
||||
// kill everything up to the next */ if it's there
|
||||
if ($this->getNext('*/')) {
|
||||
$this->getChar(); // get *
|
||||
$this->getChar(); // get /
|
||||
$char = $this->getChar(); // get next real character
|
||||
|
||||
// Now we reinsert conditional comments and YUI-style licensing comments
|
||||
if (($this->options['flaggedComments'] && $thirdCommentString === '!')
|
||||
|| ($thirdCommentString === '@')) {
|
||||
|
||||
// If conditional comments or flagged comments are not the first thing in the script
|
||||
// we need to echo a and fill it with a space before moving on.
|
||||
if ($startIndex > 0) {
|
||||
echo $this->a;
|
||||
$this->a = " ";
|
||||
|
||||
// If the comment started on a new line we let it stay on the new line
|
||||
if ($this->input[($startIndex - 1)] === "\n") {
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$endPoint = ($this->index - 1) - $startIndex;
|
||||
echo substr($this->input, $startIndex, $endPoint);
|
||||
|
||||
$this->c = $char;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$char = false;
|
||||
}
|
||||
|
||||
if ($char === false) {
|
||||
throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2));
|
||||
}
|
||||
|
||||
// if we're here c is part of the comment and therefore tossed
|
||||
$this->c = $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the index ahead to the next instance of the supplied string. If it
|
||||
* is found the first character of the string is returned and the index is set
|
||||
* to it's position.
|
||||
*
|
||||
* @param string $string
|
||||
* @return string|false Returns the first character of the string or false.
|
||||
*/
|
||||
protected function getNext($string)
|
||||
{
|
||||
// Find the next occurrence of "string" after the current position.
|
||||
$pos = strpos($this->input, $string, $this->index);
|
||||
|
||||
// If it's not there return false.
|
||||
if ($pos === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust position of index to jump ahead to the asked for string
|
||||
$this->index = $pos;
|
||||
|
||||
// Return the first character of that string.
|
||||
return $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a javascript string is detected this function crawls for the end of
|
||||
* it and saves the whole string.
|
||||
*
|
||||
* @throws \RuntimeException Unclosed strings will throw an error
|
||||
*/
|
||||
protected function saveString()
|
||||
{
|
||||
$startpos = $this->index;
|
||||
|
||||
// saveString is always called after a gets cleared, so we push b into
|
||||
// that spot.
|
||||
$this->a = $this->b;
|
||||
|
||||
// If this isn't a string we don't need to do anything.
|
||||
if (!isset($this->stringDelimiters[$this->a])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// String type is the quote used, " or '
|
||||
$stringType = $this->a;
|
||||
|
||||
// Echo out that starting quote
|
||||
echo $this->a;
|
||||
|
||||
// Loop until the string is done
|
||||
// Grab the very next character and load it into a
|
||||
while (($this->a = $this->getChar()) !== false) {
|
||||
switch ($this->a) {
|
||||
|
||||
// If the string opener (single or double quote) is used
|
||||
// output it and break out of the while loop-
|
||||
// The string is finished!
|
||||
case $stringType:
|
||||
break 2;
|
||||
|
||||
// New lines in strings without line delimiters are bad- actual
|
||||
// new lines will be represented by the string \n and not the actual
|
||||
// character, so those will be treated just fine using the switch
|
||||
// block below.
|
||||
case "\n":
|
||||
if ($stringType === '`') {
|
||||
echo $this->a;
|
||||
} else {
|
||||
throw new \RuntimeException('Unclosed string at position: ' . $startpos);
|
||||
}
|
||||
break;
|
||||
|
||||
// Escaped characters get picked up here. If it's an escaped new line it's not really needed
|
||||
case '\\':
|
||||
|
||||
// a is a slash. We want to keep it, and the next character,
|
||||
// unless it's a new line. New lines as actual strings will be
|
||||
// preserved, but escaped new lines should be reduced.
|
||||
$this->b = $this->getChar();
|
||||
|
||||
// If b is a new line we discard a and b and restart the loop.
|
||||
if ($this->b === "\n") {
|
||||
break;
|
||||
}
|
||||
|
||||
// echo out the escaped character and restart the loop.
|
||||
echo $this->a . $this->b;
|
||||
break;
|
||||
|
||||
|
||||
// Since we're not dealing with any special cases we simply
|
||||
// output the character and continue our loop.
|
||||
default:
|
||||
echo $this->a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a regular expression is detected this function crawls for the end of
|
||||
* it and saves the whole regex.
|
||||
*
|
||||
* @throws \RuntimeException Unclosed regex will throw an error
|
||||
*/
|
||||
protected function saveRegex()
|
||||
{
|
||||
echo $this->a . $this->b;
|
||||
|
||||
while (($this->a = $this->getChar()) !== false) {
|
||||
if ($this->a === '/') {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->a === '\\') {
|
||||
echo $this->a;
|
||||
$this->a = $this->getChar();
|
||||
}
|
||||
|
||||
if ($this->a === "\n") {
|
||||
throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index);
|
||||
}
|
||||
|
||||
echo $this->a;
|
||||
}
|
||||
$this->b = $this->getReal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a character is alphanumeric.
|
||||
*
|
||||
* @param string $char Just one character
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isAlphaNumeric($char)
|
||||
{
|
||||
return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace patterns in the given string and store the replacement
|
||||
*
|
||||
* @param string $js The string to lock
|
||||
* @return bool
|
||||
*/
|
||||
protected function lock($js)
|
||||
{
|
||||
/* lock things like <code>"asd" + ++x;</code> */
|
||||
$lock = '"LOCK---' . crc32(time()) . '"';
|
||||
|
||||
$matches = [];
|
||||
preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
|
||||
if (empty($matches)) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
$this->locks[$lock] = $matches[2];
|
||||
|
||||
$js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
|
||||
/* -- */
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace "locks" with the original characters
|
||||
*
|
||||
* @param string $js The string to unlock
|
||||
* @return bool
|
||||
*/
|
||||
protected function unlock($js)
|
||||
{
|
||||
if (empty($this->locks)) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
foreach ($this->locks as $lock => $replacement) {
|
||||
$js = str_replace($lock, $replacement, $js);
|
||||
}
|
||||
|
||||
return $js;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* @package functions\menubar
|
||||
*/
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/block.class.php');
|
||||
|
||||
initialize_menu();
|
||||
|
||||
/**
|
||||
* Setups each block the main menubar.
|
||||
*/
|
||||
function initialize_menu()
|
||||
{
|
||||
global $page, $conf, $user, $template, $filter;
|
||||
|
||||
$menu = new BlockManager("menubar");
|
||||
|
||||
// if guest_access is disabled, we only display the menus if the user is identified
|
||||
if ($conf['guest_access'] or !is_a_guest())
|
||||
{
|
||||
$menu->load_registered_blocks();
|
||||
}
|
||||
$menu->prepare_display();
|
||||
|
||||
if ( @$page['section']=='search' and isset($page['qsearch_details']) )
|
||||
{
|
||||
$template->assign('QUERY_SEARCH', htmlspecialchars($page['qsearch_details']['q']) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------- external links
|
||||
if ( ($block=$menu->get_block('mbLinks')) and !empty($conf['links']) )
|
||||
{
|
||||
$block->data = array();
|
||||
foreach ($conf['links'] as $url => $url_data)
|
||||
{
|
||||
if (!is_array($url_data))
|
||||
{
|
||||
$url_data = array('label' => $url_data);
|
||||
}
|
||||
|
||||
if
|
||||
(
|
||||
(!isset($url_data['eval_visible']))
|
||||
or
|
||||
(eval($url_data['eval_visible']))
|
||||
)
|
||||
{
|
||||
$tpl_var = array(
|
||||
'URL' => $url,
|
||||
'LABEL' => $url_data['label']
|
||||
);
|
||||
|
||||
if (!isset($url_data['new_window']) or $url_data['new_window'])
|
||||
{
|
||||
$tpl_var['new_window'] =
|
||||
array(
|
||||
'NAME' => (isset($url_data['nw_name']) ? $url_data['nw_name'] : ''),
|
||||
'FEATURES' => (isset($url_data['nw_features']) ? $url_data['nw_features'] : '')
|
||||
);
|
||||
}
|
||||
$block->data[] = $tpl_var;
|
||||
}
|
||||
}
|
||||
if ( !empty($block->data) )
|
||||
{
|
||||
$block->template = 'menubar_links.tpl';
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------- categories
|
||||
$block = $menu->get_block('mbCategories');
|
||||
//------------------------------------------------------------------------ filter
|
||||
if ($conf['menubar_filter_icon'] and !empty($conf['filter_pages']) and get_filter_page_value('used'))
|
||||
{
|
||||
if ($filter['enabled'])
|
||||
{
|
||||
$template->assign(
|
||||
'U_STOP_FILTER',
|
||||
add_url_params(make_index_url(array()), array('filter' => 'stop'))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->assign(
|
||||
'U_START_FILTER',
|
||||
add_url_params(make_index_url(array()), array('filter' => 'start-recent-'.$user['recent_period']))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $block!=null )
|
||||
{
|
||||
$block->data = array(
|
||||
'NB_PICTURE' => $user['nb_total_images'],
|
||||
'MENU_CATEGORIES' => get_categories_menu(),
|
||||
'U_CATEGORIES' => make_index_url(array('section' => 'categories')),
|
||||
);
|
||||
$block->template = 'menubar_categories.tpl';
|
||||
}
|
||||
|
||||
//------------------------------------------------------------ related categories
|
||||
$block = $menu->get_block('mbRelatedCategories');
|
||||
|
||||
if (
|
||||
isset($page['items'])
|
||||
and count($page['items']) < $conf['related_albums_maximum_items_to_compute']
|
||||
and $block != null
|
||||
and !empty($page['items'])
|
||||
)
|
||||
{
|
||||
$exclude_cat_ids = array();
|
||||
if (isset($page['category']))
|
||||
{
|
||||
$exclude_cat_ids = array($page['category']['id']);
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
foreach ($page['combined_categories'] as $cat)
|
||||
{
|
||||
$exclude_cat_ids[] = $cat['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$block->data = array(
|
||||
'MENU_CATEGORIES' => get_related_categories_menu($page['items'], $exclude_cat_ids),
|
||||
);
|
||||
|
||||
if (!empty($block->data['MENU_CATEGORIES']) )
|
||||
{
|
||||
$block->template = 'menubar_related_categories.tpl';
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------ tags
|
||||
$block = $menu->get_block('mbTags');
|
||||
if ( $block!=null and 'picture' != script_basename() )
|
||||
{
|
||||
$tags = get_available_tags();
|
||||
usort($tags, 'tags_counter_compare');
|
||||
$tags = array_slice($tags, 0, $conf['menubar_tag_cloud_items_number']);
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$block->data[] = array_merge(
|
||||
$tag,
|
||||
array(
|
||||
'URL' => make_index_url( array( 'tags' => array($tag) ) ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( !empty($block->data) )
|
||||
{
|
||||
$block->template = 'menubar_tags.tpl';
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------- special categories
|
||||
if ( ($block = $menu->get_block('mbSpecials')) != null )
|
||||
{
|
||||
if ( !is_a_guest() )
|
||||
{// favorites
|
||||
$block->data['favorites'] =
|
||||
array(
|
||||
'URL' => make_index_url(array('section' => 'favorites')),
|
||||
'TITLE' => l10n('display your favorites photos'),
|
||||
'NAME' => l10n('Your favorites')
|
||||
);
|
||||
}
|
||||
|
||||
$block->data['most_visited'] =
|
||||
array(
|
||||
'URL' => make_index_url(array('section' => 'most_visited')),
|
||||
'TITLE' => l10n('display most visited photos'),
|
||||
'NAME' => l10n('Most visited')
|
||||
);
|
||||
|
||||
if ($conf['rate'])
|
||||
{
|
||||
$block->data['best_rated'] =
|
||||
array(
|
||||
'URL' => make_index_url(array('section' => 'best_rated')),
|
||||
'TITLE' => l10n('display best rated photos'),
|
||||
'NAME' => l10n('Best rated')
|
||||
);
|
||||
}
|
||||
|
||||
$block->data['recent_pics'] =
|
||||
array(
|
||||
'URL' => make_index_url(array('section' => 'recent_pics')),
|
||||
'TITLE' => l10n('display most recent photos'),
|
||||
'NAME' => l10n('Recent photos'),
|
||||
);
|
||||
|
||||
$block->data['recent_cats'] =
|
||||
array(
|
||||
'URL' => make_index_url(array('section' => 'recent_cats')),
|
||||
'TITLE' => l10n('display recently updated albums'),
|
||||
'NAME' => l10n('Recent albums'),
|
||||
);
|
||||
|
||||
$block->data['random'] =
|
||||
array(
|
||||
'URL' => get_root_url().'random.php',
|
||||
'TITLE' => l10n('display a set of random photos'),
|
||||
'NAME' => l10n('Random photos'),
|
||||
'REL'=> 'rel="nofollow"'
|
||||
);
|
||||
|
||||
$block->data['calendar'] =
|
||||
array(
|
||||
'URL' =>
|
||||
make_index_url(
|
||||
array(
|
||||
'chronology_field' => ($conf['calendar_datefield']=='date_available'
|
||||
? 'posted' : 'created'),
|
||||
'chronology_style'=> 'monthly',
|
||||
'chronology_view' => 'calendar'
|
||||
)
|
||||
),
|
||||
'TITLE' => l10n('display each day with photos, month per month'),
|
||||
'NAME' => l10n('Calendar'),
|
||||
'REL'=> 'rel="nofollow"'
|
||||
);
|
||||
$block->template = 'menubar_specials.tpl';
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------- summary
|
||||
if ( ($block=$menu->get_block('mbMenu')) != null )
|
||||
{
|
||||
// quick search block will be displayed only if data['qsearch'] is set
|
||||
// to "yes"
|
||||
$block->data['qsearch']=true;
|
||||
|
||||
// tags link
|
||||
$block->data['tags'] =
|
||||
array(
|
||||
'TITLE' => l10n('display available tags'),
|
||||
'NAME' => l10n('Tags'),
|
||||
'URL'=> get_root_url().'tags.php',
|
||||
'COUNTER' => get_nb_available_tags(),
|
||||
);
|
||||
|
||||
// search link
|
||||
$block->data['search'] =
|
||||
array(
|
||||
'TITLE'=>l10n('search'),
|
||||
'NAME'=>l10n('Search'),
|
||||
'URL'=> get_root_url().'search.php',
|
||||
'REL'=> 'rel="search"'
|
||||
);
|
||||
|
||||
if ($conf['activate_comments'])
|
||||
{
|
||||
// comments link
|
||||
$block->data['comments'] =
|
||||
array(
|
||||
'TITLE'=>l10n('display last user comments'),
|
||||
'NAME'=>l10n('Comments'),
|
||||
'URL'=> get_root_url().'comments.php',
|
||||
'COUNTER' => get_nb_available_comments(),
|
||||
);
|
||||
}
|
||||
|
||||
// about link
|
||||
$block->data['about'] =
|
||||
array(
|
||||
'TITLE' => l10n('About Piwigo'),
|
||||
'NAME' => l10n('About'),
|
||||
'URL' => get_root_url().'about.php',
|
||||
);
|
||||
|
||||
// notification
|
||||
$block->data['rss'] =
|
||||
array(
|
||||
'TITLE'=>l10n('RSS feed'),
|
||||
'NAME'=>l10n('Notification'),
|
||||
'URL'=> get_root_url().'notification.php',
|
||||
'REL'=> 'rel="nofollow"'
|
||||
);
|
||||
$block->template = 'menubar_menu.tpl';
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------- identification
|
||||
if (is_a_guest())
|
||||
{
|
||||
$template->assign(
|
||||
array(
|
||||
'U_LOGIN' => get_root_url().'identification.php',
|
||||
'U_LOST_PASSWORD' => get_root_url().'password.php',
|
||||
'AUTHORIZE_REMEMBERING' => $conf['authorize_remembering']
|
||||
)
|
||||
);
|
||||
if ($conf['allow_user_registration'])
|
||||
{
|
||||
$template->assign( 'U_REGISTER', get_root_url().'register.php');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->assign('USERNAME', stripslashes($user['username']));
|
||||
if (is_autorize_status(ACCESS_CLASSIC))
|
||||
{
|
||||
$template->assign('U_PROFILE', get_root_url().'profile.php');
|
||||
}
|
||||
|
||||
// the logout link has no meaning with Apache authentication : it is not
|
||||
// possible to logout with this kind of authentication.
|
||||
if (!$conf['apache_authentication'])
|
||||
{
|
||||
$template->assign('U_LOGOUT', get_root_url().'?act=logout');
|
||||
}
|
||||
if (is_admin())
|
||||
{
|
||||
$template->assign('U_ADMIN', get_root_url().'admin.php');
|
||||
}
|
||||
}
|
||||
if ( ($block=$menu->get_block('mbIdentification')) != null )
|
||||
{
|
||||
$block->template = 'menubar_identification.tpl';
|
||||
}
|
||||
$menu->apply('MENUBAR', 'menubar.tpl' );
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
Copyright (c) 2012 Matthias Mullie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
in
|
||||
public
|
||||
extends
|
||||
private
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
@@ -0,0 +1,26 @@
|
||||
do
|
||||
in
|
||||
let
|
||||
new
|
||||
var
|
||||
case
|
||||
else
|
||||
enum
|
||||
void
|
||||
with
|
||||
class
|
||||
const
|
||||
yield
|
||||
delete
|
||||
export
|
||||
import
|
||||
public
|
||||
static
|
||||
typeof
|
||||
extends
|
||||
package
|
||||
private
|
||||
function
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
@@ -0,0 +1,63 @@
|
||||
do
|
||||
if
|
||||
in
|
||||
for
|
||||
let
|
||||
new
|
||||
try
|
||||
var
|
||||
case
|
||||
else
|
||||
enum
|
||||
eval
|
||||
null
|
||||
this
|
||||
true
|
||||
void
|
||||
with
|
||||
break
|
||||
catch
|
||||
class
|
||||
const
|
||||
false
|
||||
super
|
||||
throw
|
||||
while
|
||||
yield
|
||||
delete
|
||||
export
|
||||
import
|
||||
public
|
||||
return
|
||||
static
|
||||
switch
|
||||
typeof
|
||||
default
|
||||
extends
|
||||
finally
|
||||
package
|
||||
private
|
||||
continue
|
||||
debugger
|
||||
function
|
||||
arguments
|
||||
interface
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
abstract
|
||||
boolean
|
||||
byte
|
||||
char
|
||||
double
|
||||
final
|
||||
float
|
||||
goto
|
||||
int
|
||||
long
|
||||
native
|
||||
short
|
||||
synchronized
|
||||
throws
|
||||
transient
|
||||
volatile
|
||||
@@ -0,0 +1,46 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
%
|
||||
=
|
||||
+=
|
||||
-=
|
||||
*=
|
||||
/=
|
||||
%=
|
||||
<<=
|
||||
>>=
|
||||
>>>=
|
||||
&=
|
||||
^=
|
||||
|=
|
||||
&
|
||||
|
|
||||
^
|
||||
~
|
||||
<<
|
||||
>>
|
||||
>>>
|
||||
==
|
||||
===
|
||||
!=
|
||||
!==
|
||||
>
|
||||
<
|
||||
>=
|
||||
<=
|
||||
&&
|
||||
||
|
||||
!
|
||||
.
|
||||
[
|
||||
]
|
||||
?
|
||||
:
|
||||
,
|
||||
;
|
||||
(
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
%
|
||||
=
|
||||
+=
|
||||
-=
|
||||
*=
|
||||
/=
|
||||
%=
|
||||
<<=
|
||||
>>=
|
||||
>>>=
|
||||
&=
|
||||
^=
|
||||
|=
|
||||
&
|
||||
|
|
||||
^
|
||||
<<
|
||||
>>
|
||||
>>>
|
||||
==
|
||||
===
|
||||
!=
|
||||
!==
|
||||
>
|
||||
<
|
||||
>=
|
||||
<=
|
||||
&&
|
||||
||
|
||||
.
|
||||
[
|
||||
]
|
||||
?
|
||||
:
|
||||
,
|
||||
;
|
||||
(
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
%
|
||||
=
|
||||
+=
|
||||
-=
|
||||
*=
|
||||
/=
|
||||
%=
|
||||
<<=
|
||||
>>=
|
||||
>>>=
|
||||
&=
|
||||
^=
|
||||
|=
|
||||
&
|
||||
|
|
||||
^
|
||||
~
|
||||
<<
|
||||
>>
|
||||
>>>
|
||||
==
|
||||
===
|
||||
!=
|
||||
!==
|
||||
>
|
||||
<
|
||||
>=
|
||||
<=
|
||||
&&
|
||||
||
|
||||
!
|
||||
.
|
||||
[
|
||||
?
|
||||
:
|
||||
,
|
||||
;
|
||||
(
|
||||
{
|
||||
@@ -0,0 +1,18 @@
|
||||
Copyright (c) 2015 Matthias Mullie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\PathConverter;
|
||||
|
||||
/**
|
||||
* Convert paths relative from 1 file to another.
|
||||
*
|
||||
* E.g.
|
||||
* ../../images/icon.jpg relative to /css/imports/icons.css
|
||||
* becomes
|
||||
* ../images/icon.jpg relative to /css/minified.css
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
|
||||
*
|
||||
* @author Matthias Mullie <pathconverter@mullie.eu>
|
||||
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
class Converter implements ConverterInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $from;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $to;
|
||||
|
||||
/**
|
||||
* @param string $from The original base path (directory, not file!)
|
||||
* @param string $to The new base path (directory, not file!)
|
||||
* @param string $root Root directory (defaults to `getcwd`)
|
||||
*/
|
||||
public function __construct($from, $to, $root = '')
|
||||
{
|
||||
$shared = $this->shared($from, $to);
|
||||
if ($shared === '') {
|
||||
// when both paths have nothing in common, one of them is probably
|
||||
// absolute while the other is relative
|
||||
$root = $root ?: getcwd();
|
||||
$from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
|
||||
$to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
|
||||
|
||||
// or traveling the tree via `..`
|
||||
// attempt to resolve path, or assume it's fine if it doesn't exist
|
||||
$from = @realpath($from) ?: $from;
|
||||
$to = @realpath($to) ?: $to;
|
||||
}
|
||||
|
||||
$from = $this->dirname($from);
|
||||
$to = $this->dirname($to);
|
||||
|
||||
$from = $this->normalize($from);
|
||||
$to = $this->normalize($to);
|
||||
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize($path)
|
||||
{
|
||||
// deal with different operating systems' directory structure
|
||||
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
|
||||
|
||||
// remove leading current directory.
|
||||
if (substr($path, 0, 2) === './') {
|
||||
$path = substr($path, 2);
|
||||
}
|
||||
|
||||
// remove references to current directory in the path.
|
||||
$path = str_replace('/./', '/', $path);
|
||||
|
||||
/*
|
||||
* Example:
|
||||
* /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
|
||||
* to
|
||||
* /home/forkcms/frontend/core/layout/images/img.gif
|
||||
*/
|
||||
do {
|
||||
$path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
|
||||
} while ($count);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out the shared path of 2 locations.
|
||||
*
|
||||
* Example:
|
||||
* /home/forkcms/frontend/core/layout/images/img.gif
|
||||
* and
|
||||
* /home/forkcms/frontend/cache/minified_css
|
||||
* share
|
||||
* /home/forkcms/frontend
|
||||
*
|
||||
* @param string $path1
|
||||
* @param string $path2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shared($path1, $path2)
|
||||
{
|
||||
// $path could theoretically be empty (e.g. no path is given), in which
|
||||
// case it shouldn't expand to array(''), which would compare to one's
|
||||
// root /
|
||||
$path1 = $path1 ? explode('/', $path1) : array();
|
||||
$path2 = $path2 ? explode('/', $path2) : array();
|
||||
|
||||
$shared = array();
|
||||
|
||||
// compare paths & strip identical ancestors
|
||||
foreach ($path1 as $i => $chunk) {
|
||||
if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
|
||||
$shared[] = $chunk;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return implode('/', $shared);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert paths relative from 1 file to another.
|
||||
*
|
||||
* E.g.
|
||||
* ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
|
||||
* should become:
|
||||
* ../../core/layout/images/img.gif relative to
|
||||
* /home/forkcms/frontend/cache/minified_css
|
||||
*
|
||||
* @param string $path The relative path that needs to be converted
|
||||
*
|
||||
* @return string The new relative path
|
||||
*/
|
||||
public function convert($path)
|
||||
{
|
||||
// quit early if conversion makes no sense
|
||||
if ($this->from === $this->to) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$path = $this->normalize($path);
|
||||
// if we're not dealing with a relative path, just return absolute
|
||||
if (strpos($path, '/') === 0) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// normalize paths
|
||||
$path = $this->normalize($this->from.'/'.$path);
|
||||
|
||||
// strip shared ancestor paths
|
||||
$shared = $this->shared($path, $this->to);
|
||||
$path = mb_substr($path, mb_strlen($shared));
|
||||
$to = mb_substr($this->to, mb_strlen($shared));
|
||||
|
||||
// add .. for every directory that needs to be traversed to new path
|
||||
$to = str_repeat('../', count(array_filter(explode('/', $to))));
|
||||
|
||||
return $to.ltrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the directory name from a path.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function dirname($path)
|
||||
{
|
||||
if (@is_file($path)) {
|
||||
return dirname($path);
|
||||
}
|
||||
|
||||
if (@is_dir($path)) {
|
||||
return rtrim($path, '/');
|
||||
}
|
||||
|
||||
// no known file/dir, start making assumptions
|
||||
|
||||
// ends in / = dir
|
||||
if (mb_substr($path, -1) === '/') {
|
||||
return rtrim($path, '/');
|
||||
}
|
||||
|
||||
// has a dot in the name, likely a file
|
||||
if (preg_match('/.*\..*$/', basename($path)) !== 0) {
|
||||
return dirname($path);
|
||||
}
|
||||
|
||||
// you're on your own here!
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\PathConverter;
|
||||
|
||||
/**
|
||||
* Convert file paths.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
|
||||
*
|
||||
* @author Matthias Mullie <pathconverter@mullie.eu>
|
||||
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
interface ConverterInterface
|
||||
{
|
||||
/**
|
||||
* Convert file paths.
|
||||
*
|
||||
* @param string $path The path to be converted
|
||||
*
|
||||
* @return string The new path
|
||||
*/
|
||||
public function convert($path);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\PathConverter;
|
||||
|
||||
/**
|
||||
* Don't convert paths.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
|
||||
*
|
||||
* @author Matthias Mullie <pathconverter@mullie.eu>
|
||||
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
class NoConverter implements ConverterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convert($path)
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,897 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CSS Minifier.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
use MatthiasMullie\Minify\Exceptions\FileImportException;
|
||||
use MatthiasMullie\Minify\Exceptions\PatternMatchException;
|
||||
use MatthiasMullie\PathConverter\Converter;
|
||||
use MatthiasMullie\PathConverter\ConverterInterface;
|
||||
|
||||
/**
|
||||
* CSS minifier.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
class CSS extends Minify
|
||||
{
|
||||
/**
|
||||
* @var int maximum inport size in kB
|
||||
*/
|
||||
protected $maxImportSize = 5;
|
||||
|
||||
/**
|
||||
* @var string[] valid import extensions
|
||||
*/
|
||||
protected $importExtensions = array(
|
||||
'gif' => 'data:image/gif',
|
||||
'png' => 'data:image/png',
|
||||
'jpe' => 'data:image/jpeg',
|
||||
'jpg' => 'data:image/jpeg',
|
||||
'jpeg' => 'data:image/jpeg',
|
||||
'svg' => 'data:image/svg+xml',
|
||||
'woff' => 'data:application/x-font-woff',
|
||||
'woff2' => 'data:application/x-font-woff2',
|
||||
'avif' => 'data:image/avif',
|
||||
'apng' => 'data:image/apng',
|
||||
'webp' => 'data:image/webp',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'xbm' => 'image/x-xbitmap',
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the maximum size if files to be imported.
|
||||
*
|
||||
* Files larger than this size (in kB) will not be imported into the CSS.
|
||||
* Importing files into the CSS as data-uri will save you some connections,
|
||||
* but we should only import relatively small decorative images so that our
|
||||
* CSS file doesn't get too bulky.
|
||||
*
|
||||
* @param int $size Size in kB
|
||||
*/
|
||||
public function setMaxImportSize($size)
|
||||
{
|
||||
$this->maxImportSize = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of extensions to be imported into the CSS (to save network
|
||||
* connections).
|
||||
* Keys of the array should be the file extensions & respective values
|
||||
* should be the data type.
|
||||
*
|
||||
* @param string[] $extensions Array of file extensions
|
||||
*/
|
||||
public function setImportExtensions(array $extensions)
|
||||
{
|
||||
$this->importExtensions = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move any import statements to the top.
|
||||
*
|
||||
* @param string $content Nearly finished CSS content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function moveImportsToTop($content)
|
||||
{
|
||||
if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
|
||||
// remove from content
|
||||
foreach ($matches[0] as $import) {
|
||||
$content = str_replace($import, '', $content);
|
||||
}
|
||||
|
||||
// add to top
|
||||
$content = implode(';', $matches[2]) . ';' . trim($content, ';');
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine CSS from import statements.
|
||||
*
|
||||
* \@import's will be loaded and their content merged into the original file,
|
||||
* to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to combine imports for
|
||||
* @param string $content The CSS content to combine imports for
|
||||
* @param string[] $parents Parent paths, for circular reference checks
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws FileImportException
|
||||
*/
|
||||
protected function combineImports($source, $content, $parents)
|
||||
{
|
||||
$importRegexes = array(
|
||||
// @import url(xxx)
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
# (optional) open path enclosure
|
||||
(?P<quotes>["\']?)
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# (optional) close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
|
||||
// @import 'xxx'
|
||||
'/
|
||||
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative imports in css
|
||||
$matches = array();
|
||||
foreach ($importRegexes as $importRegex) {
|
||||
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
// get the path for the file that will be imported
|
||||
$importPath = dirname($source) . '/' . $match['path'];
|
||||
|
||||
// only replace the import with the content if we can grab the
|
||||
// content of the file
|
||||
if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if current file was not imported previously in the same
|
||||
// import chain.
|
||||
if (in_array($importPath, $parents)) {
|
||||
throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.');
|
||||
}
|
||||
|
||||
// grab referenced file & minify it (which may include importing
|
||||
// yet other @import statements recursively)
|
||||
$minifier = new self($importPath);
|
||||
$minifier->setMaxImportSize($this->maxImportSize);
|
||||
$minifier->setImportExtensions($this->importExtensions);
|
||||
$importContent = $minifier->execute($source, $parents);
|
||||
|
||||
// check if this is only valid for certain media
|
||||
if (!empty($match['media'])) {
|
||||
$importContent = '@media ' . $match['media'] . '{' . $importContent . '}';
|
||||
}
|
||||
|
||||
// add to replacement array
|
||||
$search[] = $match[0];
|
||||
$replace[] = $importContent;
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
return str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import files into the CSS, base64-ized.
|
||||
*
|
||||
* @url(image.jpg) images will be loaded and their content merged into the
|
||||
* original file, to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to import files for
|
||||
* @param string $content The CSS content to import files for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function importFiles($source, $content)
|
||||
{
|
||||
$regex = '/url\((["\']?)(.+?)\\1\)/i';
|
||||
if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
$extension = substr(strrchr($match[2], '.'), 1);
|
||||
if ($extension && !array_key_exists($extension, $this->importExtensions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the path for the file that will be imported
|
||||
$path = $match[2];
|
||||
$path = dirname($source) . '/' . $path;
|
||||
|
||||
// only replace the import with the content if we're able to get
|
||||
// the content of the file, and it's relatively small
|
||||
if ($this->canImportFile($path) && $this->canImportBySize($path)) {
|
||||
// grab content && base64-ize
|
||||
$importContent = $this->load($path);
|
||||
$importContent = base64_encode($importContent);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
$replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
$content = str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data.
|
||||
* Perform CSS optimizations.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to
|
||||
* @param string[] $parents Parent paths, for circular reference checks
|
||||
*
|
||||
* @return string The minified data
|
||||
*
|
||||
* @throws PatternMatchException
|
||||
*/
|
||||
public function execute($path = null, $parents = array())
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// loop CSS data (raw data and files)
|
||||
foreach ($this->data as $source => $css) {
|
||||
/*
|
||||
* Let's first take out strings & comments, since we can't just
|
||||
* remove whitespace anywhere. If whitespace occurs inside a string,
|
||||
* we should leave it alone. E.g.:
|
||||
* p { content: "a test" }
|
||||
*/
|
||||
$this->extractStrings();
|
||||
$this->stripComments();
|
||||
$this->extractMath();
|
||||
$this->extractCustomProperties();
|
||||
$css = $this->replace($css);
|
||||
|
||||
$css = $this->stripWhitespace($css);
|
||||
$css = $this->convertLegacyColors($css);
|
||||
$css = $this->cleanupModernColors($css);
|
||||
$css = $this->shortenHEXColors($css);
|
||||
$css = $this->shortenZeroes($css);
|
||||
$css = $this->shortenFontWeights($css);
|
||||
$css = $this->stripEmptyTags($css);
|
||||
|
||||
// restore the string we've extracted earlier
|
||||
$css = $this->restoreExtractedData($css);
|
||||
|
||||
$source = is_int($source) ? '' : $source;
|
||||
$parents = $source ? array_merge($parents, array($source)) : $parents;
|
||||
$css = $this->combineImports($source, $css, $parents);
|
||||
$css = $this->importFiles($source, $css);
|
||||
|
||||
/*
|
||||
* If we'll save to a new path, we'll have to fix the relative paths
|
||||
* to be relative no longer to the source file, but to the new path.
|
||||
* If we don't write to a file, fall back to same path so no
|
||||
* conversion happens (because we still want it to go through most
|
||||
* of the move code, which also addresses url() & @import syntax...)
|
||||
*/
|
||||
$converter = $this->getPathConverter($source, $path ?: $source);
|
||||
$css = $this->move($converter, $css);
|
||||
|
||||
// combine css
|
||||
$content .= $css;
|
||||
}
|
||||
|
||||
$content = $this->moveImportsToTop($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving a css file should update all relative urls.
|
||||
* Relative references (e.g. ../images/image.gif) in a certain css file,
|
||||
* will have to be updated when a file is being saved at another location
|
||||
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
||||
*
|
||||
* @param ConverterInterface $converter Relative path converter
|
||||
* @param string $content The CSS content to update relative urls for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function move(ConverterInterface $converter, $content)
|
||||
{
|
||||
/*
|
||||
* Relative path references will usually be enclosed by url(). @import
|
||||
* is an exception, where url() is not necessary around the path (but is
|
||||
* allowed).
|
||||
* This *could* be 1 regular expression, where both regular expressions
|
||||
* in this array are on different sides of a |. But we're using named
|
||||
* patterns in both regexes, the same name on both regexes. This is only
|
||||
* possible with a (?J) modifier, but that only works after a fairly
|
||||
* recent PCRE version. That's why I'm doing 2 separate regular
|
||||
* expressions & combining the matches after executing of both.
|
||||
*/
|
||||
$relativeRegexes = array(
|
||||
// url(xxx)
|
||||
'/
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
/ix',
|
||||
|
||||
// @import "xxx"
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# we don\'t have to check for @import url(), because the
|
||||
# condition above will already catch these
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative urls in css
|
||||
$matches = array();
|
||||
foreach ($relativeRegexes as $relativeRegex) {
|
||||
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop all urls
|
||||
foreach ($matches as $match) {
|
||||
// determine if it's a url() or an @import match
|
||||
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
||||
|
||||
$url = $match['path'];
|
||||
if ($this->canImportByPath($url)) {
|
||||
// attempting to interpret GET-params makes no sense, so let's discard them for awhile
|
||||
$params = strrchr($url, '?');
|
||||
$url = $params ? substr($url, 0, -strlen($params)) : $url;
|
||||
|
||||
// fix relative url
|
||||
$url = $converter->convert($url);
|
||||
|
||||
// now that the path has been converted, re-apply GET-params
|
||||
$url .= $params;
|
||||
}
|
||||
|
||||
/*
|
||||
* Urls with control characters above 0x7e should be quoted.
|
||||
* According to Mozilla's parser, whitespace is only allowed at the
|
||||
* end of unquoted urls.
|
||||
* Urls with `)` (as could happen with data: uris) should also be
|
||||
* quoted to avoid being confused for the url() closing parentheses.
|
||||
* And urls with a # have also been reported to cause issues.
|
||||
* Urls with quotes inside should also remain escaped.
|
||||
*
|
||||
* @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
|
||||
* @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
|
||||
* @see https://github.com/matthiasmullie/minify/issues/193
|
||||
*/
|
||||
$url = trim($url);
|
||||
if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
|
||||
$url = $match['quotes'] . $url . $match['quotes'];
|
||||
}
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
if ($type === 'url') {
|
||||
$replace[] = 'url(' . $url . ')';
|
||||
} elseif ($type === 'import') {
|
||||
$replace[] = '@import "' . $url . '"';
|
||||
}
|
||||
}
|
||||
|
||||
// replace urls
|
||||
return str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand HEX color codes.
|
||||
* #FF0000FF -> #f00 -> red
|
||||
* #FF00FF00 -> transparent.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the HEX color codes for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenHexColors($content)
|
||||
{
|
||||
// shorten repeating patterns within HEX ..
|
||||
$content = preg_replace('/(?<=[: ])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3(?:([0-9a-f])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
|
||||
|
||||
// remove alpha channel if it's pointless ..
|
||||
$content = preg_replace('/(?<=[: ])#([0-9a-f]{6})ff(?=[; }])/i', '#$1', $content);
|
||||
$content = preg_replace('/(?<=[: ])#([0-9a-f]{3})f(?=[; }])/i', '#$1', $content);
|
||||
|
||||
// replace `transparent` with shortcut ..
|
||||
$content = preg_replace('/(?<=[: ])#[0-9a-f]{6}00(?=[; }])/i', '#fff0', $content);
|
||||
|
||||
$colors = array(
|
||||
// make these more readable
|
||||
'#00f' => 'blue',
|
||||
'#dc143c' => 'crimson',
|
||||
'#0ff' => 'cyan',
|
||||
'#8b0000' => 'darkred',
|
||||
'#696969' => 'dimgray',
|
||||
'#ff69b4' => 'hotpink',
|
||||
'#0f0' => 'lime',
|
||||
'#fdf5e6' => 'oldlace',
|
||||
'#87ceeb' => 'skyblue',
|
||||
'#d8bfd8' => 'thistle',
|
||||
// we can shorten some even more by replacing them with their color name
|
||||
'#f0ffff' => 'azure',
|
||||
'#f5f5dc' => 'beige',
|
||||
'#ffe4c4' => 'bisque',
|
||||
'#a52a2a' => 'brown',
|
||||
'#ff7f50' => 'coral',
|
||||
'#ffd700' => 'gold',
|
||||
'#808080' => 'gray',
|
||||
'#008000' => 'green',
|
||||
'#4b0082' => 'indigo',
|
||||
'#fffff0' => 'ivory',
|
||||
'#f0e68c' => 'khaki',
|
||||
'#faf0e6' => 'linen',
|
||||
'#800000' => 'maroon',
|
||||
'#000080' => 'navy',
|
||||
'#808000' => 'olive',
|
||||
'#ffa500' => 'orange',
|
||||
'#da70d6' => 'orchid',
|
||||
'#cd853f' => 'peru',
|
||||
'#ffc0cb' => 'pink',
|
||||
'#dda0dd' => 'plum',
|
||||
'#800080' => 'purple',
|
||||
'#f00' => 'red',
|
||||
'#fa8072' => 'salmon',
|
||||
'#a0522d' => 'sienna',
|
||||
'#c0c0c0' => 'silver',
|
||||
'#fffafa' => 'snow',
|
||||
'#d2b48c' => 'tan',
|
||||
'#008080' => 'teal',
|
||||
'#ff6347' => 'tomato',
|
||||
'#ee82ee' => 'violet',
|
||||
'#f5deb3' => 'wheat',
|
||||
// or the other way around
|
||||
'black' => '#000',
|
||||
'fuchsia' => '#f0f',
|
||||
'magenta' => '#f0f',
|
||||
'white' => '#fff',
|
||||
'yellow' => '#ff0',
|
||||
// and also `transparent`
|
||||
'transparent' => '#fff0',
|
||||
);
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i',
|
||||
function ($match) use ($colors) {
|
||||
return $colors[strtolower($match[0])];
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB|HSL color codes.
|
||||
* rgb(255,0,0,.5) -> rgb(255 0 0 / .5).
|
||||
* rgb(255,0,0) -> #f00.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the RGB color codes for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function convertLegacyColors($content)
|
||||
{
|
||||
/*
|
||||
https://drafts.csswg.org/css-color/#color-syntax-legacy
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl
|
||||
*/
|
||||
|
||||
// convert legacy color syntax
|
||||
$content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content);
|
||||
$content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*\)/i', '$1($2 $3 $4)', $content);
|
||||
$content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content);
|
||||
$content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*\)/i', '$1($2 $3 $4)', $content);
|
||||
|
||||
// convert `rgb` to `hex`
|
||||
$dec = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])';
|
||||
|
||||
return preg_replace_callback(
|
||||
"/rgb\($dec $dec $dec\)/i",
|
||||
function ($match) {
|
||||
return sprintf('#%02x%02x%02x', $match[1], $match[2], $match[3]);
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup RGB|HSL|HWB|LCH|LAB
|
||||
* rgb(255 0 0 / 1) -> rgb(255 0 0).
|
||||
* rgb(255 0 0 / 0) -> transparent.
|
||||
*
|
||||
* @param string $content The CSS content to cleanup HSL|HWB|LCH|LAB
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function cleanupModernColors($content)
|
||||
{
|
||||
/*
|
||||
https://drafts.csswg.org/css-color/#color-syntax-modern
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab
|
||||
*/
|
||||
$tag = '(rgb|hsl|hwb|(?:(?:ok)?(?:lch|lab)))';
|
||||
|
||||
// remove alpha channel if it's pointless ..
|
||||
$content = preg_replace('/' . $tag . '\(\s*([^\s)]+)\s+([^\s)]+)\s+([^\s)]+)\s+\/\s+1(?:(?:\.\d?)*|00%)?\s*\)/i', '$1($2 $3 $4)', $content);
|
||||
|
||||
// replace `transparent` with shortcut ..
|
||||
$content = preg_replace('/' . $tag . '\(\s*[^\s)]+\s+[^\s)]+\s+[^\s)]+\s+\/\s+0(?:[\.0%]*)?\s*\)/i', '#fff0', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten CSS font weights.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the font weights for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenFontWeights($content)
|
||||
{
|
||||
$weights = array(
|
||||
'normal' => 400,
|
||||
'bold' => 700,
|
||||
);
|
||||
|
||||
$callback = function ($match) use ($weights) {
|
||||
return $match[1] . $weights[$match[2]];
|
||||
};
|
||||
|
||||
return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand 0 values to plain 0, instead of e.g. -0em.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the zero values for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenZeroes($content)
|
||||
{
|
||||
// we don't want to strip units in `calc()` expressions:
|
||||
// `5px - 0px` is valid, but `5px - 0` is not
|
||||
// `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
|
||||
// `10 * 0` is invalid
|
||||
// we've extracted calcs earlier, so we don't need to worry about this
|
||||
|
||||
// reusable bits of code throughout these regexes:
|
||||
// before & after are used to make sure we don't match lose unintended
|
||||
// 0-like values (e.g. in #000, or in http://url/1.0)
|
||||
// units can be stripped from 0 values, or used to recognize non 0
|
||||
// values (where wa may be able to strip a .0 suffix)
|
||||
$before = '(?<=[:(, ])';
|
||||
$after = '(?=[ ,);}])';
|
||||
$units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
|
||||
|
||||
// strip units after zeroes (0px -> 0)
|
||||
// NOTE: it should be safe to remove all units for a 0 value, but in
|
||||
// practice, Webkit (especially Safari) seems to stumble over at least
|
||||
// 0%, potentially other units as well. Only stripping 'px' for now.
|
||||
// @see https://github.com/matthiasmullie/minify/issues/60
|
||||
$content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content);
|
||||
|
||||
// strip 0-digits (.0 -> 0)
|
||||
$content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content);
|
||||
// strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
|
||||
$content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content);
|
||||
// strip trailing 0: 50.00 -> 50, 50.00px -> 50px
|
||||
$content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content);
|
||||
// strip leading 0: 0.1 -> .1, 01.1 -> 1.1
|
||||
$content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content);
|
||||
|
||||
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
|
||||
$content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content);
|
||||
|
||||
// IE doesn't seem to understand a unitless flex-basis value (correct -
|
||||
// it goes against the spec), so let's add it in again (make it `%`,
|
||||
// which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
|
||||
// @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
|
||||
$content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
|
||||
$content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip empty tags from source code.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function stripEmptyTags($content)
|
||||
{
|
||||
$content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
|
||||
$content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip comments from source code.
|
||||
*/
|
||||
protected function stripComments()
|
||||
{
|
||||
$this->stripMultilineComments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip whitespace.
|
||||
*
|
||||
* @param string $content The CSS content to strip the whitespace for
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws PatternMatchException
|
||||
*/
|
||||
protected function stripWhitespace($content)
|
||||
{
|
||||
// remove leading & trailing whitespace
|
||||
$content = $this->pregReplace('/^\s*/m', '', $content);
|
||||
$content = $this->pregReplace('/\s*$/m', '', $content);
|
||||
|
||||
// replace newlines with a single space
|
||||
$content = $this->pregReplace('/\s+/', ' ', $content);
|
||||
|
||||
// remove whitespace around meta characters
|
||||
// inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
|
||||
$content = $this->pregReplace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
|
||||
$content = $this->pregReplace('/([\[(:>\+])\s+/', '$1', $content);
|
||||
$content = $this->pregReplace('/\s+([\]\)>\+])/', '$1', $content);
|
||||
$content = $this->pregReplace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
|
||||
|
||||
// whitespace around + and - can only be stripped inside some pseudo-
|
||||
// classes, like `:nth-child(3+2n)`
|
||||
// not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
|
||||
// selectors like `div.weird- p`
|
||||
$pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
|
||||
$content = $this->pregReplace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
|
||||
|
||||
// remove semicolon/whitespace followed by closing bracket
|
||||
$content = str_replace(';}', '}', $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a preg_replace and check for errors.
|
||||
*
|
||||
* @param string $pattern Pattern
|
||||
* @param string $replacement Replacement
|
||||
* @param string $subject String to process
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws PatternMatchException
|
||||
*/
|
||||
protected function pregReplace($pattern, $replacement, $subject)
|
||||
{
|
||||
$result = preg_replace($pattern, $replacement, $subject);
|
||||
if ($result === null) {
|
||||
throw PatternMatchException::fromLastError("Failed to replace with pattern '$pattern'");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all occurrences of functions that may contain math, where
|
||||
* whitespace around operators needs to be preserved (e.g. calc, clamp).
|
||||
*/
|
||||
protected function extractMath()
|
||||
{
|
||||
$functions = array('calc', 'clamp', 'min', 'max');
|
||||
$pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m';
|
||||
|
||||
// PHP only supports $this inside anonymous functions since 5.4
|
||||
$minifier = $this;
|
||||
$callback = function ($match) use ($minifier, $pattern, &$callback) {
|
||||
$function = $match[1];
|
||||
$length = strlen($match[2]);
|
||||
$expr = '';
|
||||
$opened = 0;
|
||||
|
||||
// the regular expression for extracting math has 1 significant problem:
|
||||
// it can't determine the correct closing parenthesis...
|
||||
// instead, it'll match a larger portion of code to where it's certain that
|
||||
// the calc() musts have ended, and we'll figure out which is the correct
|
||||
// closing parenthesis here, by counting how many have opened
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$char = $match[2][$i];
|
||||
$expr .= $char;
|
||||
if ($char === '(') {
|
||||
++$opened;
|
||||
} elseif ($char === ')' && --$opened === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// now that we've figured out where the calc() starts and ends, extract it
|
||||
$count = count($minifier->extracted);
|
||||
$placeholder = 'math(' . $count . ')';
|
||||
$minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')';
|
||||
|
||||
// and since we've captured more code than required, we may have some leftover
|
||||
// calc() in here too - go recursive on the remaining but of code to go figure
|
||||
// that out and extract what is needed
|
||||
$rest = $minifier->str_replace_first($function . $expr, '', $match[0]);
|
||||
$rest = preg_replace_callback($pattern, $callback, $rest);
|
||||
|
||||
return $placeholder . $rest;
|
||||
};
|
||||
|
||||
$this->registerPattern($pattern, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace custom properties, whose values may be used in scenarios where
|
||||
* we wouldn't want them to be minified (e.g. inside calc).
|
||||
*/
|
||||
protected function extractCustomProperties()
|
||||
{
|
||||
// PHP only supports $this inside anonymous functions since 5.4
|
||||
$minifier = $this;
|
||||
$this->registerPattern(
|
||||
'/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m',
|
||||
function ($match) use ($minifier) {
|
||||
$placeholder = '--custom-' . count($minifier->extracted) . ':0';
|
||||
$minifier->extracted[$placeholder] = $match[1] . ':' . trim($match[2]);
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file is small enough to be imported.
|
||||
*
|
||||
* @param string $path The path to the file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportBySize($path)
|
||||
{
|
||||
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file a file can be imported, going by the path.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportByPath($path)
|
||||
{
|
||||
return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a converter to update relative paths to be relative to the new
|
||||
* destination.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
*
|
||||
* @return ConverterInterface
|
||||
*/
|
||||
protected function getPathConverter($source, $target)
|
||||
{
|
||||
return new Converter($source, $target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base Exception.
|
||||
*
|
||||
* @deprecated Use Exceptions\BasicException instead
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* Base Exception Class.
|
||||
*
|
||||
* @deprecated Use Exceptions\BasicException instead
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
abstract class Exception extends \Exception {}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Basic exception.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
use MatthiasMullie\Minify\Exception;
|
||||
|
||||
/**
|
||||
* Basic Exception Class.
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
abstract class BasicException extends Exception {}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* File Import Exception.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
/**
|
||||
* File Import Exception Class.
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class FileImportException extends BasicException {}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* IO Exception.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
/**
|
||||
* IO Exception Class.
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class IOException extends BasicException {}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Pattern match exception.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Ere Maijala <ere.maijala@helsinki.fi>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify\Exceptions;
|
||||
|
||||
/**
|
||||
* Pattern Match Exception Class.
|
||||
*
|
||||
* @author Ere Maijala <ere.maijala@helsinki.fi>
|
||||
*/
|
||||
class PatternMatchException extends BasicException
|
||||
{
|
||||
/**
|
||||
* Create an exception from preg_last_error.
|
||||
*
|
||||
* @param string $msg Error message
|
||||
*/
|
||||
public static function fromLastError($msg)
|
||||
{
|
||||
$msg .= ': Error ' . preg_last_error();
|
||||
if (PHP_MAJOR_VERSION >= 8) {
|
||||
$msg .= ' - ' . preg_last_error_msg();
|
||||
}
|
||||
|
||||
return new PatternMatchException($msg);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,571 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract minifier class.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
use MatthiasMullie\Minify\Exceptions\IOException;
|
||||
use MatthiasMullie\Minify\Exceptions\PatternMatchException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
/**
|
||||
* Abstract minifier class.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
abstract class Minify
|
||||
{
|
||||
/**
|
||||
* The data to be minified.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Array of patterns to match.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $patterns = array();
|
||||
|
||||
/**
|
||||
* This array will hold content of strings and regular expressions that have
|
||||
* been extracted from the JS source code, so we can reliably match "code",
|
||||
* without having to worry about potential "code-like" characters inside.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $extracted = array();
|
||||
|
||||
/**
|
||||
* Init the minify class - optionally, code may be passed along already.
|
||||
*/
|
||||
public function __construct(/* $data = null, ... */)
|
||||
{
|
||||
// it's possible to add the source through the constructor as well ;)
|
||||
if (func_num_args()) {
|
||||
call_user_func_array(array($this, 'add'), func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file or straight-up code to be minified.
|
||||
*
|
||||
* @param string|string[] $data
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function add($data /* $data = null, ... */)
|
||||
{
|
||||
// bogus "usage" of parameter $data: scrutinizer warns this variable is
|
||||
// not used (we're using func_get_args instead to support overloading),
|
||||
// but it still needs to be defined because it makes no sense to have
|
||||
// this function without argument :)
|
||||
$args = array($data) + func_get_args();
|
||||
|
||||
// this method can be overloaded
|
||||
foreach ($args as $data) {
|
||||
if (is_array($data)) {
|
||||
call_user_func_array(array($this, 'add'), $data);
|
||||
continue;
|
||||
}
|
||||
|
||||
// redefine var
|
||||
$data = (string) $data;
|
||||
|
||||
// load data
|
||||
$value = $this->load($data);
|
||||
$key = ($data != $value) ? $data : count($this->data);
|
||||
|
||||
// replace CR linefeeds etc.
|
||||
// @see https://github.com/matthiasmullie/minify/pull/139
|
||||
$value = str_replace(array("\r\n", "\r"), "\n", $value);
|
||||
|
||||
// store data
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to be minified.
|
||||
*
|
||||
* @param string|string[] $data
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public function addFile($data /* $data = null, ... */)
|
||||
{
|
||||
// bogus "usage" of parameter $data: scrutinizer warns this variable is
|
||||
// not used (we're using func_get_args instead to support overloading),
|
||||
// but it still needs to be defined because it makes no sense to have
|
||||
// this function without argument :)
|
||||
$args = array($data) + func_get_args();
|
||||
|
||||
// this method can be overloaded
|
||||
foreach ($args as $path) {
|
||||
if (is_array($path)) {
|
||||
call_user_func_array(array($this, 'addFile'), $path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// redefine var
|
||||
$path = (string) $path;
|
||||
|
||||
// check if we can read the file
|
||||
if (!$this->canImportFile($path)) {
|
||||
throw new IOException('The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
$this->add($path);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data & (optionally) saves it to a file.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to
|
||||
*
|
||||
* @return string The minified data
|
||||
*/
|
||||
public function minify($path = null)
|
||||
{
|
||||
$content = $this->execute($path);
|
||||
|
||||
// save to path
|
||||
if ($path !== null) {
|
||||
$this->save($content, $path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify & gzip the data & (optionally) saves it to a file.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to
|
||||
* @param int[optional] $level Compression level, from 0 to 9
|
||||
*
|
||||
* @return string The minified & gzipped data
|
||||
*/
|
||||
public function gzip($path = null, $level = 9)
|
||||
{
|
||||
$content = $this->execute($path);
|
||||
$content = gzencode($content, $level, FORCE_GZIP);
|
||||
|
||||
// save to path
|
||||
if ($path !== null) {
|
||||
$this->save($content, $path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data & write it to a CacheItemInterface object.
|
||||
*
|
||||
* @param CacheItemInterface $item Cache item to write the data to
|
||||
*
|
||||
* @return CacheItemInterface Cache item with the minifier data
|
||||
*/
|
||||
public function cache(CacheItemInterface $item)
|
||||
{
|
||||
$content = $this->execute();
|
||||
$item->set($content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to
|
||||
*
|
||||
* @return string The minified data
|
||||
*/
|
||||
abstract public function execute($path = null);
|
||||
|
||||
/**
|
||||
* Load data.
|
||||
*
|
||||
* @param string $data Either a path to a file or the content itself
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function load($data)
|
||||
{
|
||||
// check if the data is a file
|
||||
if ($this->canImportFile($data)) {
|
||||
$data = file_get_contents($data);
|
||||
|
||||
// strip BOM, if any
|
||||
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to file.
|
||||
*
|
||||
* @param string $content The minified data
|
||||
* @param string $path The path to save the minified data to
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function save($content, $path)
|
||||
{
|
||||
$handler = $this->openFileForWriting($path);
|
||||
|
||||
$this->writeToFile($handler, $content);
|
||||
|
||||
@fclose($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a pattern to execute against the source content.
|
||||
*
|
||||
* If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work.
|
||||
* If you need that functionality, use a callback instead.
|
||||
*
|
||||
* @param string $pattern PCRE pattern
|
||||
* @param string|callable $replacement Replacement value for matched pattern
|
||||
*/
|
||||
protected function registerPattern($pattern, $replacement = '')
|
||||
{
|
||||
// study the pattern, we'll execute it more than once
|
||||
$pattern .= 'S';
|
||||
|
||||
$this->patterns[] = array($pattern, $replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Both JS and CSS use the same form of multi-line comment, so putting the common code here.
|
||||
*/
|
||||
protected function stripMultilineComments()
|
||||
{
|
||||
$minifier = $this;
|
||||
// Pattern for matching comments that we want to preserve
|
||||
$keepPattern = '/^
|
||||
# comment content
|
||||
(?:
|
||||
# either starts with an !
|
||||
!
|
||||
|
|
||||
# or, after some number of characters which do not end the comment
|
||||
(?:(?!\*\/).)*?
|
||||
|
||||
# there is either a @license or @preserve tag
|
||||
@(?:license|preserve)
|
||||
)
|
||||
/ixs';
|
||||
$callback = function ($match) use ($minifier, $keepPattern) {
|
||||
if (preg_match($keepPattern, $match[1])) {
|
||||
// Preserve the comment
|
||||
$count = count($minifier->extracted);
|
||||
$placeholder = '/*' . $count . '*/';
|
||||
$minifier->extracted[$placeholder] = $match[0];
|
||||
} else {
|
||||
// Discard the comment but keep any single line feed
|
||||
$placeholder = strncmp($match[0], "\n", 1) === 0 || substr($match[0], -1) === "\n"
|
||||
? "\n"
|
||||
: '';
|
||||
}
|
||||
|
||||
return $placeholder;
|
||||
};
|
||||
|
||||
$this->registerPattern('/\n?\/\*(.*?)\*\/\n?/s', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* We can't "just" run some regular expressions against JavaScript: it's a
|
||||
* complex language. E.g. having an occurrence of // xyz would be a comment,
|
||||
* unless it's used within a string. Of you could have something that looks
|
||||
* like a 'string', but inside a comment.
|
||||
* The only way to accurately replace these pieces is to traverse the JS one
|
||||
* character at a time and try to find whatever starts first.
|
||||
*
|
||||
* @param string $content The content to replace patterns in
|
||||
*
|
||||
* @return string The (manipulated) content
|
||||
*
|
||||
* @throws PatternMatchException
|
||||
*/
|
||||
protected function replace($content)
|
||||
{
|
||||
$contentLength = strlen($content);
|
||||
$output = '';
|
||||
$processedOffset = 0;
|
||||
$positions = array_fill(0, count($this->patterns), -1);
|
||||
$matches = array();
|
||||
|
||||
while ($processedOffset < $contentLength) {
|
||||
// find first match for all patterns
|
||||
foreach ($this->patterns as $i => $pattern) {
|
||||
list($pattern, $replacement) = $pattern;
|
||||
|
||||
// we can safely ignore patterns for positions we've unset earlier,
|
||||
// because we know these won't show up anymore
|
||||
if (array_key_exists($i, $positions) == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// no need to re-run matches that are still in the part of the
|
||||
// content that hasn't been processed
|
||||
if ($positions[$i] >= $processedOffset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = null;
|
||||
$matchResult = preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset);
|
||||
if ($matchResult) {
|
||||
$matches[$i] = $match;
|
||||
|
||||
// we'll store the match position as well; that way, we
|
||||
// don't have to redo all preg_matches after changing only
|
||||
// the first (we'll still know where those others are)
|
||||
$positions[$i] = $match[0][1];
|
||||
} else {
|
||||
if ($matchResult === false) {
|
||||
throw PatternMatchException::fromLastError(
|
||||
"Failed to match pattern '$pattern' at $processedOffset"
|
||||
);
|
||||
}
|
||||
// if the pattern couldn't be matched, there's no point in
|
||||
// executing it again in later runs on this same content;
|
||||
// ignore this one until we reach end of content
|
||||
unset($matches[$i], $positions[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
// no more matches to find: everything's been processed, break out
|
||||
if (!$matches) {
|
||||
// output the remaining content
|
||||
$output .= substr($content, $processedOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
// see which of the patterns actually found the first thing (we'll
|
||||
// only want to execute that one, since we're unsure if what the
|
||||
// other found was not inside what the first found)
|
||||
$matchOffset = min($positions);
|
||||
$firstPattern = array_search($matchOffset, $positions);
|
||||
$match = $matches[$firstPattern];
|
||||
|
||||
// execute the pattern that matches earliest in the content string
|
||||
list(, $replacement) = $this->patterns[$firstPattern];
|
||||
|
||||
// add the part of the input between $processedOffset and the first match;
|
||||
// that content wasn't matched by anything
|
||||
$output .= substr($content, $processedOffset, $matchOffset - $processedOffset);
|
||||
// add the replacement for the match
|
||||
$output .= $this->executeReplacement($replacement, $match);
|
||||
// advance $processedOffset past the match
|
||||
$processedOffset = $matchOffset + strlen($match[0][0]);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* If $replacement is a callback, execute it, passing in the match data.
|
||||
* If it's a string, just pass it through.
|
||||
*
|
||||
* @param string|callable $replacement Replacement value
|
||||
* @param array $match Match data, in PREG_OFFSET_CAPTURE form
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function executeReplacement($replacement, $match)
|
||||
{
|
||||
if (!is_callable($replacement)) {
|
||||
return $replacement;
|
||||
}
|
||||
// convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects
|
||||
foreach ($match as &$matchItem) {
|
||||
$matchItem = $matchItem[0];
|
||||
}
|
||||
|
||||
return $replacement($match);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strings are a pattern we need to match, in order to ignore potential
|
||||
* code-like content inside them, but we just want all of the string
|
||||
* content to remain untouched.
|
||||
*
|
||||
* This method will replace all string content with simple STRING#
|
||||
* placeholder text, so we've rid all strings from characters that may be
|
||||
* misinterpreted. Original string content will be saved in $this->extracted
|
||||
* and after doing all other minifying, we can restore the original content
|
||||
* via restoreStrings().
|
||||
*
|
||||
* @param string[optional] $chars
|
||||
* @param string[optional] $placeholderPrefix
|
||||
*/
|
||||
protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
|
||||
{
|
||||
// PHP only supports $this inside anonymous functions since 5.4
|
||||
$minifier = $this;
|
||||
$callback = function ($match) use ($minifier, $placeholderPrefix) {
|
||||
// check the second index here, because the first always contains a quote
|
||||
if ($match[2] === '') {
|
||||
/*
|
||||
* Empty strings need no placeholder; they can't be confused for
|
||||
* anything else anyway.
|
||||
* But we still needed to match them, for the extraction routine
|
||||
* to skip over this particular string.
|
||||
*/
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
$count = count($minifier->extracted);
|
||||
$placeholder = $match[1] . $placeholderPrefix . $count . $match[1];
|
||||
$minifier->extracted[$placeholder] = $match[1] . $match[2] . $match[1];
|
||||
|
||||
return $placeholder;
|
||||
};
|
||||
|
||||
/*
|
||||
* Quantifier {0,65535} is used instead of *? to avoid exceeding
|
||||
* backtrack limit with large strings. 65535 is the maximum allowed
|
||||
* (see https://www.php.net/manual/en/regexp.reference.repetition.php)
|
||||
* and should be well sufficient for string representations here.
|
||||
*
|
||||
* The \\ messiness explained:
|
||||
* * Don't count ' or " as end-of-string if it's escaped (has backslash
|
||||
* in front of it)
|
||||
* * Unless... that backslash itself is escaped (another leading slash),
|
||||
* in which case it's no longer escaping the ' or "
|
||||
* * So there can be either no backslash, or an even number
|
||||
* * multiply all of that times 4, to account for the escaping that has
|
||||
* to be done to pass the backslash into the PHP string without it being
|
||||
* considered as escape-char (times 2) and to get it in the regex,
|
||||
* escaped (times 2)
|
||||
*/
|
||||
|
||||
$this->registerPattern('/([' . $chars . '])(.{0,65535}?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will restore all extracted data (strings, regexes) that were
|
||||
* replaced with placeholder text in extract*(). The original content was
|
||||
* saved in $this->extracted.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function restoreExtractedData($content)
|
||||
{
|
||||
if (!$this->extracted) {
|
||||
// nothing was extracted, nothing to restore
|
||||
return $content;
|
||||
}
|
||||
|
||||
$content = strtr($content, $this->extracted);
|
||||
|
||||
$this->extracted = array();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the path is a regular file and can be read.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportFile($path)
|
||||
{
|
||||
$parsed = parse_url($path);
|
||||
if (
|
||||
// file is elsewhere
|
||||
isset($parsed['host'])
|
||||
// file responds to queries (may change, or need to bypass cache)
|
||||
|| isset($parsed['query'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
|
||||
}
|
||||
// catch openbasedir exceptions which are not caught by @ on is_file()
|
||||
catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open file specified by $path for writing.
|
||||
*
|
||||
* @param string $path The path to the file
|
||||
*
|
||||
* @return resource Specifier for the target file
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function openFileForWriting($path)
|
||||
{
|
||||
if ($path === '' || ($handler = @fopen($path, 'w')) === false) {
|
||||
throw new IOException('The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
|
||||
*
|
||||
* @param resource $handler The resource to write to
|
||||
* @param string $content The content to write
|
||||
* @param string $path The path to the file (for exception printing only)
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function writeToFile($handler, $content, $path = '')
|
||||
{
|
||||
if (
|
||||
!is_resource($handler)
|
||||
|| ($result = @fwrite($handler, $content)) === false
|
||||
|| ($result < strlen($content))
|
||||
) {
|
||||
throw new IOException('The file "' . $path . '" could not be written to. Check your disk space and file permissions.');
|
||||
}
|
||||
}
|
||||
|
||||
protected static function str_replace_first($search, $replace, $subject)
|
||||
{
|
||||
$pos = strpos($subject, $search);
|
||||
if ($pos !== false) {
|
||||
return substr_replace($subject, $replace, $pos, strlen($search));
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
|
||||
// The "No Photo Yet" feature: if you have no photo yet in your gallery, the
|
||||
// gallery displays only a big box to show you the way for adding your first
|
||||
// photos
|
||||
if (
|
||||
!(defined('IN_ADMIN') and IN_ADMIN) // no message inside administration
|
||||
and script_basename() != 'identification' // keep the ability to login
|
||||
and script_basename() != 'password' // keep the ability to reset password
|
||||
and script_basename() != 'ws' // keep the ability to discuss with web API
|
||||
and script_basename() != 'popuphelp' // keep the ability to display help popups
|
||||
and (is_a_guest() or is_admin()) // normal users are not concerned by no_photo_yet
|
||||
and !isset($_SESSION['no_photo_yet']) // temporary hide
|
||||
)
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM '.IMAGES_TABLE.'
|
||||
;';
|
||||
list($nb_photos) = pwg_db_fetch_row(pwg_query($query));
|
||||
if (0 == $nb_photos)
|
||||
{
|
||||
// make sure we don't use the mobile theme, which is not compatible with
|
||||
// the "no photo yet" feature
|
||||
$template = new Template(PHPWG_ROOT_PATH.'themes', $user['theme']);
|
||||
|
||||
if (isset($_GET['no_photo_yet']))
|
||||
{
|
||||
if ('browse' == $_GET['no_photo_yet'])
|
||||
{
|
||||
$_SESSION['no_photo_yet'] = 'browse';
|
||||
redirect(make_index_url());
|
||||
exit();
|
||||
}
|
||||
|
||||
if ('deactivate' == $_GET['no_photo_yet'])
|
||||
{
|
||||
conf_update_param('no_photo_yet', 'false');
|
||||
redirect(make_index_url());
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset='.get_pwg_charset());
|
||||
$template->set_filenames(array('no_photo_yet'=>'no_photo_yet.tpl'));
|
||||
|
||||
if (is_admin())
|
||||
{
|
||||
$url = $conf['no_photo_yet_url'];
|
||||
if (substr($url, 0, 4) != 'http')
|
||||
{
|
||||
$url = get_root_url().$url;
|
||||
}
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'step' => 2,
|
||||
'intro' => l10n(
|
||||
'Hello %s, your Piwigo photo gallery is empty!',
|
||||
$user['username']
|
||||
),
|
||||
'next_step_url' => $url,
|
||||
'deactivate_url' => get_root_url().'?no_photo_yet=deactivate',
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'step' => 1,
|
||||
'U_LOGIN' => 'identification.php',
|
||||
'deactivate_url' => get_root_url().'?no_photo_yet=browse',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
trigger_notify('loc_end_no_photo_yet');
|
||||
|
||||
$template->pparse('no_photo_yet');
|
||||
exit();
|
||||
}
|
||||
else
|
||||
{
|
||||
conf_update_param('no_photo_yet', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
//
|
||||
// Start output of page
|
||||
//
|
||||
$template->set_filenames(array('header'=>'header.tpl'));
|
||||
|
||||
trigger_notify('loc_begin_page_header');
|
||||
|
||||
$show_mobile_app_banner = conf_get_param('show_mobile_app_banner_in_gallery', false);
|
||||
if (defined('IN_ADMIN') and IN_ADMIN)
|
||||
{
|
||||
$show_mobile_app_banner = conf_get_param('show_mobile_app_banner_in_admin', true);
|
||||
}
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'GALLERY_TITLE' =>
|
||||
isset($page['gallery_title']) ?
|
||||
$page['gallery_title'] : $conf['gallery_title'],
|
||||
|
||||
'PAGE_BANNER' =>
|
||||
trigger_change(
|
||||
'render_page_banner',
|
||||
str_replace(
|
||||
'%gallery_title%',
|
||||
$conf['gallery_title'],
|
||||
isset($page['page_banner']) ? $page['page_banner'] : $conf['page_banner']
|
||||
)
|
||||
),
|
||||
|
||||
'BODY_ID' =>
|
||||
isset($page['body_id']) ?
|
||||
$page['body_id'] : '',
|
||||
|
||||
'CONTENT_ENCODING' => get_pwg_charset(),
|
||||
'PAGE_TITLE' => strip_tags($title),
|
||||
|
||||
'U_HOME' => get_gallery_home_url(),
|
||||
|
||||
'LEVEL_SEPARATOR' => $conf['level_separator'],
|
||||
|
||||
'SHOW_MOBILE_APP_BANNER' => $show_mobile_app_banner,
|
||||
|
||||
'BODY_CLASSES' => $page['body_classes'],
|
||||
|
||||
'BODY_DATA' => json_encode($page['body_data']),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// Header notes
|
||||
if ( !empty($header_notes) )
|
||||
{
|
||||
$template->assign('header_notes',$header_notes);
|
||||
}
|
||||
|
||||
// No referencing is required
|
||||
if ( !$conf['meta_ref'] )
|
||||
{
|
||||
$page['meta_robots']['noindex'] = 1;
|
||||
$page['meta_robots']['nofollow'] = 1;
|
||||
}
|
||||
|
||||
if ( !empty($page['meta_robots']) )
|
||||
{
|
||||
$template->append('head_elements',
|
||||
'<meta name="robots" content="'
|
||||
.implode(',', array_keys($page['meta_robots']))
|
||||
.'">'
|
||||
);
|
||||
}
|
||||
if ( !isset($page['meta_robots']['noindex']) )
|
||||
{
|
||||
$template->assign('meta_ref',1);
|
||||
}
|
||||
|
||||
// refresh
|
||||
if ( isset( $refresh ) and intval($refresh) >= 0
|
||||
and isset( $url_link ) )
|
||||
{
|
||||
$template->assign(
|
||||
array(
|
||||
'page_refresh' => array(
|
||||
'TIME' => $refresh,
|
||||
'U_REFRESH' => $url_link
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
trigger_notify('loc_end_page_header');
|
||||
|
||||
header('Content-Type: text/html; charset='.get_pwg_charset());
|
||||
$template->parse('header');
|
||||
|
||||
trigger_notify('loc_after_page_header');
|
||||
?>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
$template->set_filenames(array('tail'=>'footer.tpl'));
|
||||
|
||||
trigger_notify('loc_begin_page_tail');
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'VERSION' => $conf['show_version'] ? PHPWG_VERSION : '',
|
||||
'PHPWG_URL' => defined('PHPWG_URL') ? str_replace('http:', 'https:', PHPWG_URL) : '',
|
||||
));
|
||||
|
||||
//--------------------------------------------------------------------- contact
|
||||
|
||||
if (!is_a_guest())
|
||||
{
|
||||
$template->assign(
|
||||
'CONTACT_MAIL', get_webmaster_mail_address()
|
||||
);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------- update notification
|
||||
if ($conf['update_notify_check_period'] > 0)
|
||||
{
|
||||
$check_for_updates = false;
|
||||
if (isset($conf['update_notify_last_check']))
|
||||
{
|
||||
if (strtotime($conf['update_notify_last_check']) < strtotime($conf['update_notify_check_period'].' seconds ago'))
|
||||
{
|
||||
$check_for_updates = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$check_for_updates = true;
|
||||
}
|
||||
|
||||
if ($check_for_updates)
|
||||
{
|
||||
$exec_id = pwg_unique_exec_begins('check_for_updates');
|
||||
if (false !== $exec_id)
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
|
||||
include_once(PHPWG_ROOT_PATH.'admin/include/updates.class.php');
|
||||
$updates = new updates();
|
||||
$updates->notify_piwigo_new_versions();
|
||||
|
||||
pwg_unique_exec_ends('check_for_updates', $exec_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_piwigo_infos();
|
||||
|
||||
//------------------------------------------------------------- generation time
|
||||
$debug_vars = array();
|
||||
|
||||
if ($conf['show_queries'])
|
||||
{
|
||||
$debug_vars = array_merge($debug_vars, array('QUERIES_LIST' => $debug) );
|
||||
}
|
||||
|
||||
if ($conf['show_gt'])
|
||||
{
|
||||
if (!isset($page['count_queries']))
|
||||
{
|
||||
$page['count_queries'] = 0;
|
||||
$page['queries_time'] = 0;
|
||||
}
|
||||
$time = get_elapsed_time($t2, get_moment());
|
||||
|
||||
$debug_vars = array_merge($debug_vars,
|
||||
array('TIME' => $time,
|
||||
'NB_QUERIES' => $page['count_queries'],
|
||||
'SQL_TIME' => number_format($page['queries_time'],3,'.',' ').' s')
|
||||
);
|
||||
}
|
||||
|
||||
$template->assign('debug', $debug_vars );
|
||||
|
||||
//------------------------------------------------------------- mobile version
|
||||
if ( !empty($conf['mobile_theme']) && (get_device() != 'desktop' || mobile_theme()))
|
||||
{
|
||||
$template->assign('TOGGLE_MOBILE_THEME_URL',
|
||||
add_url_params(
|
||||
htmlspecialchars($_SERVER['REQUEST_URI']),
|
||||
array('mobile' => mobile_theme() ? 'false' : 'true')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
trigger_notify('loc_end_page_tail');
|
||||
//
|
||||
// Generate the page
|
||||
//
|
||||
$template->parse('tail');
|
||||
$template->p();
|
||||
?>
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
/**
|
||||
* Portable PHP password hashing framework.
|
||||
* @package phpass
|
||||
* @since 2.5.0
|
||||
* @version 0.3 / WordPress
|
||||
* @link http://www.openwall.com/phpass/
|
||||
*/
|
||||
|
||||
#
|
||||
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
|
||||
# the public domain. Revised in subsequent years, still public domain.
|
||||
#
|
||||
# There's absolutely no warranty.
|
||||
#
|
||||
# Please be sure to update the Version line if you edit this file in any way.
|
||||
# It is suggested that you leave the main version number intact, but indicate
|
||||
# your project name (after the slash) and add your own revision information.
|
||||
#
|
||||
# Please do not change the "private" password hashing method implemented in
|
||||
# here, thereby making your hashes incompatible. However, if you must, please
|
||||
# change the hash type identifier (the "$P$") to something different.
|
||||
#
|
||||
# Obviously, since this code is in the public domain, the above are not
|
||||
# requirements (there can be none), but merely suggestions.
|
||||
#
|
||||
|
||||
/**
|
||||
* Portable PHP password hashing framework.
|
||||
*
|
||||
* @package phpass
|
||||
* @version 0.3 / WordPress
|
||||
* @link http://www.openwall.com/phpass/
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class PasswordHash {
|
||||
var $itoa64;
|
||||
var $iteration_count_log2;
|
||||
var $portable_hashes;
|
||||
var $random_state;
|
||||
|
||||
/**
|
||||
* PHP5 constructor.
|
||||
*/
|
||||
function __construct( $iteration_count_log2, $portable_hashes )
|
||||
{
|
||||
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
|
||||
$iteration_count_log2 = 8;
|
||||
$this->iteration_count_log2 = $iteration_count_log2;
|
||||
|
||||
$this->portable_hashes = $portable_hashes;
|
||||
|
||||
$this->random_state = microtime() . uniqid(rand(), TRUE); // removed getmypid() for compatibility reasons
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP4 constructor.
|
||||
*/
|
||||
public function PasswordHash( $iteration_count_log2, $portable_hashes ) {
|
||||
self::__construct( $iteration_count_log2, $portable_hashes );
|
||||
}
|
||||
|
||||
function get_random_bytes($count)
|
||||
{
|
||||
$output = '';
|
||||
if ( @is_readable('/dev/urandom') &&
|
||||
($fh = @fopen('/dev/urandom', 'rb'))) {
|
||||
$output = fread($fh, $count);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
if (strlen($output) < $count) {
|
||||
$output = '';
|
||||
for ($i = 0; $i < $count; $i += 16) {
|
||||
$this->random_state =
|
||||
md5(microtime() . $this->random_state);
|
||||
$output .=
|
||||
pack('H*', md5($this->random_state));
|
||||
}
|
||||
$output = substr($output, 0, $count);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function encode64($input, $count)
|
||||
{
|
||||
$output = '';
|
||||
$i = 0;
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $this->itoa64[$value & 0x3f];
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 8;
|
||||
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 16;
|
||||
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_private($input)
|
||||
{
|
||||
$output = '$P$';
|
||||
$output .= $this->itoa64[min($this->iteration_count_log2 +
|
||||
((PHP_VERSION >= '5') ? 5 : 3), 30)];
|
||||
$output .= $this->encode64($input, 6);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function crypt_private($password, $setting)
|
||||
{
|
||||
$output = '*0';
|
||||
if (substr($setting, 0, 2) == $output)
|
||||
$output = '*1';
|
||||
|
||||
$id = substr($setting, 0, 3);
|
||||
# We use "$P$", phpBB3 uses "$H$" for the same thing
|
||||
if ($id != '$P$' && $id != '$H$')
|
||||
return $output;
|
||||
|
||||
$count_log2 = strpos($this->itoa64, $setting[3]);
|
||||
if ($count_log2 < 7 || $count_log2 > 30)
|
||||
return $output;
|
||||
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
$salt = substr($setting, 4, 8);
|
||||
if (strlen($salt) != 8)
|
||||
return $output;
|
||||
|
||||
# We're kind of forced to use MD5 here since it's the only
|
||||
# cryptographic primitive available in all versions of PHP
|
||||
# currently in use. To implement our own low-level crypto
|
||||
# in PHP would result in much worse performance and
|
||||
# consequently in lower iteration counts and hashes that are
|
||||
# quicker to crack (by non-PHP code).
|
||||
if (PHP_VERSION >= '5') {
|
||||
$hash = md5($salt . $password, TRUE);
|
||||
do {
|
||||
$hash = md5($hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
} else {
|
||||
$hash = pack('H*', md5($salt . $password));
|
||||
do {
|
||||
$hash = pack('H*', md5($hash . $password));
|
||||
} while (--$count);
|
||||
}
|
||||
|
||||
$output = substr($setting, 0, 12);
|
||||
$output .= $this->encode64($hash, 16);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_extended($input)
|
||||
{
|
||||
$count_log2 = min($this->iteration_count_log2 + 8, 24);
|
||||
# This should be odd to not reveal weak DES keys, and the
|
||||
# maximum valid value is (2**24 - 1) which is odd anyway.
|
||||
$count = (1 << $count_log2) - 1;
|
||||
|
||||
$output = '_';
|
||||
$output .= $this->itoa64[$count & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 6) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 12) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 18) & 0x3f];
|
||||
|
||||
$output .= $this->encode64($input, 3);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_blowfish($input)
|
||||
{
|
||||
# This one needs to use a different order of characters and a
|
||||
# different encoding scheme from the one in encode64() above.
|
||||
# We care because the last character in our encoded string will
|
||||
# only represent 2 bits. While two known implementations of
|
||||
# bcrypt will happily accept and correct a salt string which
|
||||
# has the 4 unused bits set to non-zero, we do not want to take
|
||||
# chances and we also do not want to waste an additional byte
|
||||
# of entropy.
|
||||
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
$output = '$2a$';
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
|
||||
$output .= '$';
|
||||
|
||||
$i = 0;
|
||||
do {
|
||||
$c1 = ord($input[$i++]);
|
||||
$output .= $itoa64[$c1 >> 2];
|
||||
$c1 = ($c1 & 0x03) << 4;
|
||||
if ($i >= 16) {
|
||||
$output .= $itoa64[$c1];
|
||||
break;
|
||||
}
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 4;
|
||||
$output .= $itoa64[$c1];
|
||||
$c1 = ($c2 & 0x0f) << 2;
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 6;
|
||||
$output .= $itoa64[$c1];
|
||||
$output .= $itoa64[$c2 & 0x3f];
|
||||
} while (1);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function HashPassword($password)
|
||||
{
|
||||
if ( strlen( $password ) > 4096 ) {
|
||||
return '*';
|
||||
}
|
||||
|
||||
$random = '';
|
||||
|
||||
if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
|
||||
$random = $this->get_random_bytes(16);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_blowfish($random));
|
||||
if (strlen($hash) == 60)
|
||||
return $hash;
|
||||
}
|
||||
|
||||
if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
|
||||
if (strlen($random) < 3)
|
||||
$random = $this->get_random_bytes(3);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_extended($random));
|
||||
if (strlen($hash) == 20)
|
||||
return $hash;
|
||||
}
|
||||
|
||||
if (strlen($random) < 6)
|
||||
$random = $this->get_random_bytes(6);
|
||||
$hash =
|
||||
$this->crypt_private($password,
|
||||
$this->gensalt_private($random));
|
||||
if (strlen($hash) == 34)
|
||||
return $hash;
|
||||
|
||||
# Returning '*' on error is safe here, but would _not_ be safe
|
||||
# in a crypt(3)-like function used _both_ for generating new
|
||||
# hashes and for validating passwords against existing hashes.
|
||||
return '*';
|
||||
}
|
||||
|
||||
function CheckPassword($password, $stored_hash)
|
||||
{
|
||||
if ( strlen( $password ) > 4096 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hash = $this->crypt_private($password, $stored_hash);
|
||||
if ($hash[0] == '*')
|
||||
$hash = crypt($password, $stored_hash);
|
||||
|
||||
return $hash === $stored_hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
if (!function_exists('gzopen') && function_exists('gzopen64'))
|
||||
{
|
||||
function gzopen(string $filename , string $mode, int $use_include_path = null)
|
||||
{
|
||||
return gzopen64($filename, $mode, $use_include_path);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
// Recursive call
|
||||
$url = '../';
|
||||
header( 'Request-URI: '.$url );
|
||||
header( 'Content-Location: '.$url );
|
||||
header( 'Location: '.$url );
|
||||
exit();
|
||||
?>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
if (!function_exists('str_starts_with'))
|
||||
{
|
||||
function str_starts_with(string $haystack, string $needle): bool
|
||||
{
|
||||
return strlen($needle) === 0 || strpos($haystack, $needle) === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer - PHP email creation and transport class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2023 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* Configure PHPMailer with DSN string.
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/Data_source_name
|
||||
*
|
||||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
|
||||
*/
|
||||
class DSNConfigurator
|
||||
{
|
||||
/**
|
||||
* Create new PHPMailer instance configured by DSN.
|
||||
*
|
||||
* @param string $dsn DSN
|
||||
* @param bool $exceptions Should we throw external exceptions?
|
||||
*
|
||||
* @return PHPMailer
|
||||
*/
|
||||
public static function mailer($dsn, $exceptions = null)
|
||||
{
|
||||
static $configurator = null;
|
||||
|
||||
if (null === $configurator) {
|
||||
$configurator = new DSNConfigurator();
|
||||
}
|
||||
|
||||
return $configurator->configure(new PHPMailer($exceptions), $dsn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure PHPMailer instance with DSN string.
|
||||
*
|
||||
* @param PHPMailer $mailer PHPMailer instance
|
||||
* @param string $dsn DSN
|
||||
*
|
||||
* @return PHPMailer
|
||||
*/
|
||||
public function configure(PHPMailer $mailer, $dsn)
|
||||
{
|
||||
$config = $this->parseDSN($dsn);
|
||||
|
||||
$this->applyConfig($mailer, $config);
|
||||
|
||||
return $mailer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse DSN string.
|
||||
*
|
||||
* @param string $dsn DSN
|
||||
*
|
||||
* @throws Exception If DSN is malformed
|
||||
*
|
||||
* @return array Configuration
|
||||
*/
|
||||
private function parseDSN($dsn)
|
||||
{
|
||||
$config = $this->parseUrl($dsn);
|
||||
|
||||
if (false === $config || !isset($config['scheme']) || !isset($config['host'])) {
|
||||
throw new Exception(
|
||||
sprintf('Malformed DSN: "%s".', $dsn)
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($config['query'])) {
|
||||
parse_str($config['query'], $config['query']);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply configuration to mailer.
|
||||
*
|
||||
* @param PHPMailer $mailer PHPMailer instance
|
||||
* @param array $config Configuration
|
||||
*
|
||||
* @throws Exception If scheme is invalid
|
||||
*/
|
||||
private function applyConfig(PHPMailer $mailer, $config)
|
||||
{
|
||||
switch ($config['scheme']) {
|
||||
case 'mail':
|
||||
$mailer->isMail();
|
||||
break;
|
||||
case 'sendmail':
|
||||
$mailer->isSendmail();
|
||||
break;
|
||||
case 'qmail':
|
||||
$mailer->isQmail();
|
||||
break;
|
||||
case 'smtp':
|
||||
case 'smtps':
|
||||
$mailer->isSMTP();
|
||||
$this->configureSMTP($mailer, $config);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".',
|
||||
$config['scheme']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($config['query'])) {
|
||||
$this->configureOptions($mailer, $config['query']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure SMTP.
|
||||
*
|
||||
* @param PHPMailer $mailer PHPMailer instance
|
||||
* @param array $config Configuration
|
||||
*/
|
||||
private function configureSMTP($mailer, $config)
|
||||
{
|
||||
$isSMTPS = 'smtps' === $config['scheme'];
|
||||
|
||||
if ($isSMTPS) {
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
}
|
||||
|
||||
$mailer->Host = $config['host'];
|
||||
|
||||
if (isset($config['port'])) {
|
||||
$mailer->Port = $config['port'];
|
||||
} elseif ($isSMTPS) {
|
||||
$mailer->Port = SMTP::DEFAULT_SECURE_PORT;
|
||||
}
|
||||
|
||||
$mailer->SMTPAuth = isset($config['user']) || isset($config['pass']);
|
||||
|
||||
if (isset($config['user'])) {
|
||||
$mailer->Username = $config['user'];
|
||||
}
|
||||
|
||||
if (isset($config['pass'])) {
|
||||
$mailer->Password = $config['pass'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure options.
|
||||
*
|
||||
* @param PHPMailer $mailer PHPMailer instance
|
||||
* @param array $options Options
|
||||
*
|
||||
* @throws Exception If option is unknown
|
||||
*/
|
||||
private function configureOptions(PHPMailer $mailer, $options)
|
||||
{
|
||||
$allowedOptions = get_object_vars($mailer);
|
||||
|
||||
unset($allowedOptions['Mailer']);
|
||||
unset($allowedOptions['SMTPAuth']);
|
||||
unset($allowedOptions['Username']);
|
||||
unset($allowedOptions['Password']);
|
||||
unset($allowedOptions['Hostname']);
|
||||
unset($allowedOptions['Port']);
|
||||
unset($allowedOptions['ErrorInfo']);
|
||||
|
||||
$allowedOptions = \array_keys($allowedOptions);
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (!in_array($key, $allowedOptions)) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Unknown option: "%s". Allowed values: "%s"',
|
||||
$key,
|
||||
implode('", "', $allowedOptions)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'AllowEmpty':
|
||||
case 'SMTPAutoTLS':
|
||||
case 'SMTPKeepAlive':
|
||||
case 'SingleTo':
|
||||
case 'UseSendmailOptions':
|
||||
case 'do_verp':
|
||||
case 'DKIM_copyHeaderFields':
|
||||
$mailer->$key = (bool) $value;
|
||||
break;
|
||||
case 'Priority':
|
||||
case 'SMTPDebug':
|
||||
case 'WordWrap':
|
||||
$mailer->$key = (int) $value;
|
||||
break;
|
||||
default:
|
||||
$mailer->$key = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a URL.
|
||||
* Wrapper for the built-in parse_url function to work around a bug in PHP 5.5.
|
||||
*
|
||||
* @param string $url URL
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function parseUrl($url)
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) {
|
||||
return parse_url($url);
|
||||
}
|
||||
|
||||
$chunks = explode('?', $url);
|
||||
if (is_array($chunks)) {
|
||||
$result = parse_url($chunks[0]);
|
||||
if (is_array($result)) {
|
||||
$result['query'] = $chunks[1];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer Exception class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2020 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* PHPMailer exception handler.
|
||||
*
|
||||
* @author Marcus Bointon <phpmailer@synchromedia.co.uk>
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
* Prettify error message output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorMessage()
|
||||
{
|
||||
return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer - PHP email creation and transport class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2020 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
use League\OAuth2\Client\Grant\RefreshToken;
|
||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
|
||||
/**
|
||||
* OAuth - OAuth2 authentication wrapper class.
|
||||
* Uses the oauth2-client package from the League of Extraordinary Packages.
|
||||
*
|
||||
* @see http://oauth2-client.thephpleague.com
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
*/
|
||||
class OAuth implements OAuthTokenProvider
|
||||
{
|
||||
/**
|
||||
* An instance of the League OAuth Client Provider.
|
||||
*
|
||||
* @var AbstractProvider
|
||||
*/
|
||||
protected $provider;
|
||||
|
||||
/**
|
||||
* The current OAuth access token.
|
||||
*
|
||||
* @var AccessToken
|
||||
*/
|
||||
protected $oauthToken;
|
||||
|
||||
/**
|
||||
* The user's email address, usually used as the login ID
|
||||
* and also the from address when sending email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oauthUserEmail = '';
|
||||
|
||||
/**
|
||||
* The client secret, generated in the app definition of the service you're connecting to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oauthClientSecret = '';
|
||||
|
||||
/**
|
||||
* The client ID, generated in the app definition of the service you're connecting to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oauthClientId = '';
|
||||
|
||||
/**
|
||||
* The refresh token, used to obtain new AccessTokens.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oauthRefreshToken = '';
|
||||
|
||||
/**
|
||||
* OAuth constructor.
|
||||
*
|
||||
* @param array $options Associative array containing
|
||||
* `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements
|
||||
*/
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->provider = $options['provider'];
|
||||
$this->oauthUserEmail = $options['userName'];
|
||||
$this->oauthClientSecret = $options['clientSecret'];
|
||||
$this->oauthClientId = $options['clientId'];
|
||||
$this->oauthRefreshToken = $options['refreshToken'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new RefreshToken.
|
||||
*
|
||||
* @return RefreshToken
|
||||
*/
|
||||
protected function getGrant()
|
||||
{
|
||||
return new RefreshToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new AccessToken.
|
||||
*
|
||||
* @return AccessToken
|
||||
*/
|
||||
protected function getToken()
|
||||
{
|
||||
return $this->provider->getAccessToken(
|
||||
$this->getGrant(),
|
||||
['refresh_token' => $this->oauthRefreshToken]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a base64-encoded OAuth token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOauth64()
|
||||
{
|
||||
//Get a new token if it's not available or has expired
|
||||
if (null === $this->oauthToken || $this->oauthToken->hasExpired()) {
|
||||
$this->oauthToken = $this->getToken();
|
||||
}
|
||||
|
||||
return base64_encode(
|
||||
'user=' .
|
||||
$this->oauthUserEmail .
|
||||
"\001auth=Bearer " .
|
||||
$this->oauthToken .
|
||||
"\001\001"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer - PHP email creation and transport class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2020 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* OAuthTokenProvider - OAuth2 token provider interface.
|
||||
* Provides base64 encoded OAuth2 auth strings for SMTP authentication.
|
||||
*
|
||||
* @see OAuth
|
||||
* @see SMTP::authenticate()
|
||||
*
|
||||
* @author Peter Scopes (pdscopes)
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
*/
|
||||
interface OAuthTokenProvider
|
||||
{
|
||||
/**
|
||||
* Generate a base64-encoded OAuth token ensuring that the access token has not expired.
|
||||
* The string to be base 64 encoded should be in the form:
|
||||
* "user=<user_email_address>\001auth=Bearer <access_token>\001\001"
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOauth64();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,467 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHPMailer POP-Before-SMTP Authentication Class.
|
||||
* PHP Version 5.5.
|
||||
*
|
||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
* @copyright 2012 - 2020 Marcus Bointon
|
||||
* @copyright 2010 - 2012 Jim Jagielski
|
||||
* @copyright 2004 - 2009 Andy Prevost
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
namespace PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* PHPMailer POP-Before-SMTP Authentication Class.
|
||||
* Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
|
||||
* 1) This class does not support APOP authentication.
|
||||
* 2) Opening and closing lots of POP3 connections can be quite slow. If you need
|
||||
* to send a batch of emails then just perform the authentication once at the start,
|
||||
* and then loop through your mail sending script. Providing this process doesn't
|
||||
* take longer than the verification period lasts on your POP3 server, you should be fine.
|
||||
* 3) This is really ancient technology; you should only need to use it to talk to very old systems.
|
||||
* 4) This POP3 class is deliberately lightweight and incomplete, implementing just
|
||||
* enough to do authentication.
|
||||
* If you want a more complete class there are other POP3 classes for PHP available.
|
||||
*
|
||||
* @author Richard Davey (original author) <rich@corephp.co.uk>
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
*/
|
||||
class POP3
|
||||
{
|
||||
/**
|
||||
* The POP3 PHPMailer Version number.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '6.8.0';
|
||||
|
||||
/**
|
||||
* Default POP3 port number.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DEFAULT_PORT = 110;
|
||||
|
||||
/**
|
||||
* Default timeout in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DEFAULT_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* POP3 class debug output mode.
|
||||
* Debug output level.
|
||||
* Options:
|
||||
* @see POP3::DEBUG_OFF: No output
|
||||
* @see POP3::DEBUG_SERVER: Server messages, connection/server errors
|
||||
* @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $do_debug = self::DEBUG_OFF;
|
||||
|
||||
/**
|
||||
* POP3 mail server hostname.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $host;
|
||||
|
||||
/**
|
||||
* POP3 port number.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $port;
|
||||
|
||||
/**
|
||||
* POP3 Timeout Value in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tval;
|
||||
|
||||
/**
|
||||
* POP3 username.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $username;
|
||||
|
||||
/**
|
||||
* POP3 password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* Resource handle for the POP3 connection socket.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $pop_conn;
|
||||
|
||||
/**
|
||||
* Are we connected?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $connected = false;
|
||||
|
||||
/**
|
||||
* Error container.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $errors = [];
|
||||
|
||||
/**
|
||||
* Line break constant.
|
||||
*/
|
||||
const LE = "\r\n";
|
||||
|
||||
/**
|
||||
* Debug level for no output.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DEBUG_OFF = 0;
|
||||
|
||||
/**
|
||||
* Debug level to show server -> client messages
|
||||
* also shows clients connection errors or errors from server
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DEBUG_SERVER = 1;
|
||||
|
||||
/**
|
||||
* Debug level to show client -> server and server -> client messages.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DEBUG_CLIENT = 2;
|
||||
|
||||
/**
|
||||
* Simple static wrapper for all-in-one POP before SMTP.
|
||||
*
|
||||
* @param string $host The hostname to connect to
|
||||
* @param int|bool $port The port number to connect to
|
||||
* @param int|bool $timeout The timeout value
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param int $debug_level
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function popBeforeSmtp(
|
||||
$host,
|
||||
$port = false,
|
||||
$timeout = false,
|
||||
$username = '',
|
||||
$password = '',
|
||||
$debug_level = 0
|
||||
) {
|
||||
$pop = new self();
|
||||
|
||||
return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate with a POP3 server.
|
||||
* A connect, login, disconnect sequence
|
||||
* appropriate for POP-before SMTP authorisation.
|
||||
*
|
||||
* @param string $host The hostname to connect to
|
||||
* @param int|bool $port The port number to connect to
|
||||
* @param int|bool $timeout The timeout value
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param int $debug_level
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
|
||||
{
|
||||
$this->host = $host;
|
||||
//If no port value provided, use default
|
||||
if (false === $port) {
|
||||
$this->port = static::DEFAULT_PORT;
|
||||
} else {
|
||||
$this->port = (int) $port;
|
||||
}
|
||||
//If no timeout value provided, use default
|
||||
if (false === $timeout) {
|
||||
$this->tval = static::DEFAULT_TIMEOUT;
|
||||
} else {
|
||||
$this->tval = (int) $timeout;
|
||||
}
|
||||
$this->do_debug = $debug_level;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
//Reset the error log
|
||||
$this->errors = [];
|
||||
//Connect
|
||||
$result = $this->connect($this->host, $this->port, $this->tval);
|
||||
if ($result) {
|
||||
$login_result = $this->login($this->username, $this->password);
|
||||
if ($login_result) {
|
||||
$this->disconnect();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//We need to disconnect regardless of whether the login succeeded
|
||||
$this->disconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a POP3 server.
|
||||
*
|
||||
* @param string $host
|
||||
* @param int|bool $port
|
||||
* @param int $tval
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connect($host, $port = false, $tval = 30)
|
||||
{
|
||||
//Are we already connected?
|
||||
if ($this->connected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
|
||||
//Rather than suppress it with @fsockopen, capture it cleanly instead
|
||||
set_error_handler([$this, 'catchWarning']);
|
||||
|
||||
if (false === $port) {
|
||||
$port = static::DEFAULT_PORT;
|
||||
}
|
||||
|
||||
//Connect to the POP3 server
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$this->pop_conn = fsockopen(
|
||||
$host, //POP3 Host
|
||||
$port, //Port #
|
||||
$errno, //Error Number
|
||||
$errstr, //Error Message
|
||||
$tval
|
||||
); //Timeout (seconds)
|
||||
//Restore the error handler
|
||||
restore_error_handler();
|
||||
|
||||
//Did we connect?
|
||||
if (false === $this->pop_conn) {
|
||||
//It would appear not...
|
||||
$this->setError(
|
||||
"Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Increase the stream time-out
|
||||
stream_set_timeout($this->pop_conn, $tval, 0);
|
||||
|
||||
//Get the POP3 server response
|
||||
$pop3_response = $this->getResponse();
|
||||
//Check for the +OK
|
||||
if ($this->checkResponse($pop3_response)) {
|
||||
//The connection is established and the POP3 server is talking
|
||||
$this->connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in to the POP3 server.
|
||||
* Does not support APOP (RFC 2828, 4949).
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function login($username = '', $password = '')
|
||||
{
|
||||
if (!$this->connected) {
|
||||
$this->setError('Not connected to POP3 server');
|
||||
return false;
|
||||
}
|
||||
if (empty($username)) {
|
||||
$username = $this->username;
|
||||
}
|
||||
if (empty($password)) {
|
||||
$password = $this->password;
|
||||
}
|
||||
|
||||
//Send the Username
|
||||
$this->sendString("USER $username" . static::LE);
|
||||
$pop3_response = $this->getResponse();
|
||||
if ($this->checkResponse($pop3_response)) {
|
||||
//Send the Password
|
||||
$this->sendString("PASS $password" . static::LE);
|
||||
$pop3_response = $this->getResponse();
|
||||
if ($this->checkResponse($pop3_response)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the POP3 server.
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
// If could not connect at all, no need to disconnect
|
||||
if ($this->pop_conn === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendString('QUIT' . static::LE);
|
||||
|
||||
// RFC 1939 shows POP3 server sending a +OK response to the QUIT command.
|
||||
// Try to get it. Ignore any failures here.
|
||||
try {
|
||||
$this->getResponse();
|
||||
} catch (Exception $e) {
|
||||
//Do nothing
|
||||
}
|
||||
|
||||
//The QUIT command may cause the daemon to exit, which will kill our connection
|
||||
//So ignore errors here
|
||||
try {
|
||||
@fclose($this->pop_conn);
|
||||
} catch (Exception $e) {
|
||||
//Do nothing
|
||||
}
|
||||
|
||||
// Clean up attributes.
|
||||
$this->connected = false;
|
||||
$this->pop_conn = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a response from the POP3 server.
|
||||
*
|
||||
* @param int $size The maximum number of bytes to retrieve
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getResponse($size = 128)
|
||||
{
|
||||
$response = fgets($this->pop_conn, $size);
|
||||
if ($this->do_debug >= self::DEBUG_SERVER) {
|
||||
echo 'Server -> Client: ', $response;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send raw data to the POP3 server.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function sendString($string)
|
||||
{
|
||||
if ($this->pop_conn) {
|
||||
if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
|
||||
echo 'Client -> Server: ', $string;
|
||||
}
|
||||
|
||||
return fwrite($this->pop_conn, $string, strlen($string));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the POP3 server response.
|
||||
* Looks for for +OK or -ERR.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkResponse($string)
|
||||
{
|
||||
if (strpos($string, '+OK') !== 0) {
|
||||
$this->setError("Server reported an error: $string");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error to the internal error store.
|
||||
* Also display debug output if it's enabled.
|
||||
*
|
||||
* @param string $error
|
||||
*/
|
||||
protected function setError($error)
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
if ($this->do_debug >= self::DEBUG_SERVER) {
|
||||
echo '<pre>';
|
||||
foreach ($this->errors as $e) {
|
||||
print_r($e);
|
||||
}
|
||||
echo '</pre>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of error messages, if any.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* POP3 connection error handler.
|
||||
*
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
*/
|
||||
protected function catchWarning($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
$this->setError(
|
||||
'Connecting to the POP3 server raised a PHP warning:' .
|
||||
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This file is included by the picture page to manage user comments
|
||||
*
|
||||
*/
|
||||
|
||||
// the picture is commentable if it belongs at least to one category which
|
||||
// is commentable
|
||||
$page['show_comments'] = false;
|
||||
foreach ($related_categories as $category)
|
||||
{
|
||||
if ($category['commentable']=='true')
|
||||
{
|
||||
$page['show_comments'] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $page['show_comments'] and isset( $_POST['content'] ) )
|
||||
{
|
||||
if ( is_a_guest() and !$conf['comments_forall'] )
|
||||
{
|
||||
die ('Session expired');
|
||||
}
|
||||
|
||||
$comm = array(
|
||||
'author' => empty(@$_POST['author']) ? '' : trim( @$_POST['author'] ),
|
||||
'content' => empty(@$_POST['content']) ? '' : trim( $_POST['content'] ),
|
||||
'website_url' => empty(@$_POST['website_url']) ? '' : trim( @$_POST['website_url'] ),
|
||||
'email' => empty(@$_POST['email']) ? '' : trim( @$_POST['email'] ),
|
||||
'image_id' => $page['image_id'],
|
||||
);
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_comment.inc.php');
|
||||
|
||||
$comment_action = insert_user_comment($comm, @$_POST['key'], $page['errors']);
|
||||
|
||||
switch ($comment_action)
|
||||
{
|
||||
case 'moderate':
|
||||
$page['infos'][] = l10n('An administrator must authorize your comment before it is visible.');
|
||||
case 'validate':
|
||||
$page['infos'][] = l10n('Your comment has been registered');
|
||||
break;
|
||||
case 'reject':
|
||||
set_status_header(403);
|
||||
$page['errors'][] = l10n('Your comment has NOT been registered because it did not pass the validation rules');
|
||||
break;
|
||||
default:
|
||||
trigger_error('Invalid comment action '.$comment_action, E_USER_WARNING);
|
||||
}
|
||||
|
||||
// allow plugins to notify what's going on
|
||||
trigger_notify( 'user_comment_insertion',
|
||||
array_merge($comm, array('action'=>$comment_action) )
|
||||
);
|
||||
}
|
||||
elseif ( isset($_POST['content']) )
|
||||
{
|
||||
set_status_header(403);
|
||||
die('ugly spammer');
|
||||
}
|
||||
|
||||
if ($page['show_comments'])
|
||||
{
|
||||
if ( !is_admin() )
|
||||
{
|
||||
$validated_clause = ' AND validated = \'true\'';
|
||||
}
|
||||
else
|
||||
{
|
||||
$validated_clause = '';
|
||||
}
|
||||
|
||||
// number of comments for this picture
|
||||
$query = '
|
||||
SELECT
|
||||
COUNT(*) AS nb_comments
|
||||
FROM '.COMMENTS_TABLE.'
|
||||
WHERE image_id = '.$page['image_id']
|
||||
.$validated_clause.'
|
||||
;';
|
||||
$row = pwg_db_fetch_assoc( pwg_query( $query ) );
|
||||
|
||||
// navigation bar creation
|
||||
if (!isset($page['start']))
|
||||
{
|
||||
$page['start'] = 0;
|
||||
}
|
||||
|
||||
$navigation_bar = create_navigation_bar(
|
||||
duplicate_picture_url(array(), array('start')),
|
||||
$row['nb_comments'],
|
||||
$page['start'],
|
||||
$conf['nb_comment_page'],
|
||||
true // We want a clean URL
|
||||
);
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'COMMENT_COUNT' => $row['nb_comments'],
|
||||
'navbar' => $navigation_bar,
|
||||
'comments' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
if ($row['nb_comments'] > 0)
|
||||
{
|
||||
// comments order (get, session, conf)
|
||||
if (!empty($_GET['comments_order']) && in_array(strtoupper($_GET['comments_order']), array('ASC', 'DESC')))
|
||||
{
|
||||
pwg_set_session_var('comments_order', $_GET['comments_order']);
|
||||
}
|
||||
$comments_order = pwg_get_session_var('comments_order', $conf['comments_order']);
|
||||
|
||||
$template->assign(array(
|
||||
'COMMENTS_ORDER_URL' => add_url_params( duplicate_picture_url(), array('comments_order'=> ($comments_order == 'ASC' ? 'DESC' : 'ASC') ) ),
|
||||
'COMMENTS_ORDER_TITLE' => $comments_order == 'ASC' ? l10n('Show latest comments first') : l10n('Show oldest comments first'),
|
||||
));
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
com.id,
|
||||
com.author,
|
||||
com.author_id,
|
||||
u.'.$conf['user_fields']['email'].' AS user_email,
|
||||
com.date,
|
||||
com.image_id,
|
||||
com.website_url,
|
||||
com.email,
|
||||
com.content,
|
||||
com.validated
|
||||
FROM '.COMMENTS_TABLE.' AS com
|
||||
LEFT JOIN '.USERS_TABLE.' AS u
|
||||
ON u.'.$conf['user_fields']['id'].' = author_id
|
||||
WHERE com.image_id = '.$page['image_id'].'
|
||||
'.$validated_clause.'
|
||||
ORDER BY com.date '.$comments_order.'
|
||||
LIMIT '.$conf['nb_comment_page'].' OFFSET '.$page['start'].'
|
||||
;';
|
||||
$result = pwg_query( $query );
|
||||
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if ($row['author'] == 'guest')
|
||||
{
|
||||
$row['author'] = l10n('guest');
|
||||
}
|
||||
|
||||
$email = null;
|
||||
if (!empty($row['user_email']))
|
||||
{
|
||||
$email = $row['user_email'];
|
||||
}
|
||||
elseif (!empty($row['email']))
|
||||
{
|
||||
$email = $row['email'];
|
||||
}
|
||||
|
||||
$tpl_comment =
|
||||
array(
|
||||
'ID' => $row['id'],
|
||||
'AUTHOR' => trigger_change('render_comment_author', $row['author']),
|
||||
'DATE' => format_date($row['date'], array('day_name','day','month','year','time')),
|
||||
'CONTENT' => trigger_change('render_comment_content',$row['content']),
|
||||
'WEBSITE_URL' => $row['website_url'],
|
||||
);
|
||||
|
||||
if (can_manage_comment('delete', $row['author_id']))
|
||||
{
|
||||
$tpl_comment['U_DELETE'] = add_url_params(
|
||||
$url_self,
|
||||
array(
|
||||
'action'=>'delete_comment',
|
||||
'comment_to_delete'=>$row['id'],
|
||||
'pwg_token' => get_pwg_token(),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (can_manage_comment('edit', $row['author_id']))
|
||||
{
|
||||
$tpl_comment['U_EDIT'] = add_url_params(
|
||||
$url_self,
|
||||
array(
|
||||
'action'=>'edit_comment',
|
||||
'comment_to_edit'=>$row['id'],
|
||||
)
|
||||
);
|
||||
if (isset($edit_comment) and ($row['id'] == $edit_comment))
|
||||
{
|
||||
$tpl_comment['IN_EDIT'] = true;
|
||||
$key = get_ephemeral_key(2, $page['image_id']);
|
||||
$tpl_comment['KEY'] = $key;
|
||||
$tpl_comment['CONTENT'] = $row['content'];
|
||||
$tpl_comment['PWG_TOKEN'] = get_pwg_token();
|
||||
$tpl_comment['U_CANCEL'] = $url_self;
|
||||
}
|
||||
}
|
||||
if (is_admin())
|
||||
{
|
||||
$tpl_comment['EMAIL'] = $email;
|
||||
|
||||
if ($row['validated'] != 'true')
|
||||
{
|
||||
$tpl_comment['U_VALIDATE'] = add_url_params(
|
||||
$url_self,
|
||||
array(
|
||||
'action' => 'validate_comment',
|
||||
'comment_to_validate' => $row['id'],
|
||||
'pwg_token' => get_pwg_token(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
$template->append('comments', $tpl_comment);
|
||||
}
|
||||
}
|
||||
|
||||
$show_add_comment_form = true;
|
||||
if (isset($edit_comment))
|
||||
{
|
||||
$show_add_comment_form = false;
|
||||
}
|
||||
if (is_a_guest() and !$conf['comments_forall'])
|
||||
{
|
||||
$show_add_comment_form = false;
|
||||
}
|
||||
|
||||
if ($show_add_comment_form)
|
||||
{
|
||||
$key = get_ephemeral_key(3, $page['image_id']);
|
||||
|
||||
$tpl_var = array(
|
||||
'F_ACTION' => $url_self,
|
||||
'KEY' => $key,
|
||||
'CONTENT' => '',
|
||||
'SHOW_AUTHOR' => !is_classic_user(),
|
||||
'AUTHOR_MANDATORY' => $conf['comments_author_mandatory'],
|
||||
'AUTHOR' => '',
|
||||
'WEBSITE_URL' => '',
|
||||
'SHOW_EMAIL' => !is_classic_user() or empty($user['email']),
|
||||
'EMAIL_MANDATORY' => $conf['comments_email_mandatory'],
|
||||
'EMAIL' => '',
|
||||
'SHOW_WEBSITE' => $conf['comments_enable_website'],
|
||||
);
|
||||
|
||||
if ('reject'==@$comment_action)
|
||||
{
|
||||
foreach( array('content', 'author', 'website_url', 'email') as $k)
|
||||
{
|
||||
$tpl_var[strtoupper($k)] = isset($_POST[$k]) ? htmlspecialchars(stripslashes(@$_POST[$k])) : '';
|
||||
}
|
||||
}
|
||||
$template->assign('comment_add', $tpl_var);
|
||||
}
|
||||
$template->set_filenames( array('comment_list' => 'comment_list.tpl'));
|
||||
$template->assign_var_from_handle('COMMENT_LIST', 'comment_list');
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This file is included by the picture page to manage picture metadata
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'/include/functions_metadata.inc.php');
|
||||
if (($conf['show_exif']) and (function_exists('exif_read_data')))
|
||||
{
|
||||
$exif_mapping = array();
|
||||
foreach ($conf['show_exif_fields'] as $field)
|
||||
{
|
||||
$exif_mapping[$field] = $field;
|
||||
}
|
||||
|
||||
$exif = get_exif_data($picture['current']['src_image']->get_path(), $exif_mapping);
|
||||
|
||||
if (count($exif) > 0)
|
||||
{
|
||||
$tpl_meta = array(
|
||||
'TITLE' => l10n('EXIF Metadata'),
|
||||
'lines' => array(),
|
||||
);
|
||||
|
||||
foreach ($conf['show_exif_fields'] as $field)
|
||||
{
|
||||
if (strpos($field, ';') === false)
|
||||
{
|
||||
// template cannot deal with an array as value, we skip it
|
||||
if (isset($exif[$field]) and !is_array($exif[$field]))
|
||||
{
|
||||
$key = $field;
|
||||
if (isset($lang['exif_field_'.$field]))
|
||||
{
|
||||
$key = $lang['exif_field_'.$field];
|
||||
}
|
||||
$tpl_meta['lines'][$key] = $exif[$field];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = explode(';', $field);
|
||||
// template cannot deal with an array as value, we skip it
|
||||
if (isset($exif[$field]) and !is_array($exif[$field]))
|
||||
{
|
||||
$key = $tokens[1];
|
||||
if (isset($lang['exif_field_'.$key]))
|
||||
{
|
||||
$key = $lang['exif_field_'.$key];
|
||||
}
|
||||
$tpl_meta['lines'][$key] = $exif[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
$template->append('metadata', $tpl_meta);
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf['show_iptc'])
|
||||
{
|
||||
$iptc = get_iptc_data($picture['current']['src_image']->get_path(), $conf['show_iptc_mapping'], ', ');
|
||||
|
||||
if (count($iptc) > 0)
|
||||
{
|
||||
$tpl_meta = array(
|
||||
'TITLE' => l10n('IPTC Metadata'),
|
||||
'lines' => array(),
|
||||
);
|
||||
|
||||
foreach ($iptc as $field => $value)
|
||||
{
|
||||
$key = $field;
|
||||
if (isset($lang[$field]))
|
||||
{
|
||||
$key = $lang[$field];
|
||||
}
|
||||
$tpl_meta['lines'][$key] = $value;
|
||||
}
|
||||
$template->append('metadata', $tpl_meta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This file is included by the picture page to manage rates
|
||||
*
|
||||
*/
|
||||
|
||||
if ($conf['rate'])
|
||||
{
|
||||
$rate_summary = array( 'count'=>0, 'score'=>$picture['current']['rating_score'], 'average'=>null );
|
||||
if ( NULL != $rate_summary['score'] )
|
||||
{
|
||||
$query = '
|
||||
SELECT COUNT(rate) AS count
|
||||
, ROUND(AVG(rate),2) AS average
|
||||
FROM '.RATE_TABLE.'
|
||||
WHERE element_id = '.$picture['current']['id'].'
|
||||
;';
|
||||
list($rate_summary['count'], $rate_summary['average']) = pwg_db_fetch_row(pwg_query($query));
|
||||
}
|
||||
$template->assign('rate_summary', $rate_summary);
|
||||
|
||||
$user_rate = null;
|
||||
if ($conf['rate_anonymous'] or is_autorize_status(ACCESS_CLASSIC) )
|
||||
{
|
||||
if ($rate_summary['count']>0)
|
||||
{
|
||||
$query = 'SELECT rate
|
||||
FROM '.RATE_TABLE.'
|
||||
WHERE element_id = '.$page['image_id'] . '
|
||||
AND user_id = '.$user['id'] ;
|
||||
|
||||
if ( !is_autorize_status(ACCESS_CLASSIC) )
|
||||
{
|
||||
$ip_components = explode('.', $_SERVER['REMOTE_ADDR']);
|
||||
if ( count($ip_components)>3 )
|
||||
{
|
||||
array_pop($ip_components);
|
||||
}
|
||||
$anonymous_id = implode ('.', $ip_components);
|
||||
$query .= ' AND anonymous_id = \''.$anonymous_id . '\'';
|
||||
}
|
||||
|
||||
$result = pwg_query($query);
|
||||
if (pwg_db_num_rows($result) > 0)
|
||||
{
|
||||
$row = pwg_db_fetch_assoc($result);
|
||||
$user_rate = $row['rate'];
|
||||
}
|
||||
}
|
||||
|
||||
$template->assign(
|
||||
'rating',
|
||||
array(
|
||||
'F_ACTION' => add_url_params(
|
||||
$url_self,
|
||||
array('action'=>'rate')
|
||||
),
|
||||
'USER_RATE'=> $user_rate,
|
||||
'marks' => $conf['rate_items']
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// see https://php.watch/versions/8.4/session_set_save_handler-alt-signature-deprecated
|
||||
class PwgSession implements SessionHandlerInterface {
|
||||
public function open(string $path, string $name): bool
|
||||
{
|
||||
return pwg_session_open($path, $name);
|
||||
}
|
||||
|
||||
public function close(): bool
|
||||
{
|
||||
return pwg_session_close();
|
||||
}
|
||||
|
||||
public function read(string $id): string
|
||||
{
|
||||
return pwg_session_read($id);
|
||||
}
|
||||
|
||||
public function write(string $id, string $data): bool
|
||||
{
|
||||
return pwg_session_write($id, $data);
|
||||
}
|
||||
|
||||
public function destroy(string $id): bool
|
||||
{
|
||||
return pwg_session_destroy($id);
|
||||
}
|
||||
|
||||
public function gc(int $max_lifetime): int
|
||||
{
|
||||
return pwg_session_gc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// see https://php.watch/versions/8.4/session_set_save_handler-alt-signature-deprecated
|
||||
// https://github.com/Piwigo/Piwigo/issues/2296
|
||||
class PwgSession implements SessionHandlerInterface {
|
||||
public function open($path, $name)
|
||||
{
|
||||
return pwg_session_open($path, $name);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
return pwg_session_close();
|
||||
}
|
||||
|
||||
public function read($id)
|
||||
{
|
||||
return pwg_session_read($id);
|
||||
}
|
||||
|
||||
public function write($id, $data)
|
||||
{
|
||||
return pwg_session_write($id, $data);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
return pwg_session_destroy($id);
|
||||
}
|
||||
|
||||
public function gc($max_lifetime)
|
||||
{
|
||||
return pwg_session_gc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,919 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
$filters_views = safe_unserialize(conf_get_param('filters_views', $conf['default_filters_views']));
|
||||
|
||||
$template->assign('display_filter', $filters_views);
|
||||
|
||||
// we add isset($page['search_details']) in this condition because it only
|
||||
// applies to regular search, not the legacy qsearch. As Piwigo 14 will still
|
||||
// be able to show an old quicksearch result, we must check this condtion too.
|
||||
if ('search' == $page['section'] and isset($page['search_details']))
|
||||
{
|
||||
$display_filters = $filters_views;
|
||||
|
||||
foreach($filters_views as $filt_name => $filt_conf){
|
||||
if(isset($filt_conf['access']))
|
||||
{
|
||||
if ($filt_conf['access'] == 'everybody' or ($filt_conf['access'] == 'admins-only' and is_admin()) or ($filt_conf['access'] == 'registered-users' and is_classic_user()))
|
||||
{
|
||||
$display_filters[$filt_name]['access'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$display_filters[$filt_name]['access'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include_once(PHPWG_ROOT_PATH.'include/functions_search.inc.php');
|
||||
|
||||
$my_search = get_search_array($page['search']);
|
||||
|
||||
$page['search_details']['forbidden'] = get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id',
|
||||
),
|
||||
"\n AND"
|
||||
);
|
||||
|
||||
// we want filters to be filled with values related to current items ONLY IF we have some filters filled
|
||||
if ($page['search_details']['has_filters_filled'])
|
||||
{
|
||||
$search_items = array(-1);
|
||||
if (!empty($page['items']))
|
||||
{
|
||||
$search_items = $page['items'];
|
||||
}
|
||||
|
||||
$search_items_clause = 'image_id IN ('.implode(',', $search_items).')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$search_items_clause = '1=1';
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['allwords']) and !($display_filters['words']['access']))
|
||||
{
|
||||
unset($my_search['fields']['allwords']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['tags']) and $display_filters['tags']['access'])
|
||||
{
|
||||
$filter_tags = array();
|
||||
|
||||
// TODO calling get_available_tags(), with lots of photos/albums/tags may cost time,
|
||||
// we should reuse the result if already executed (for building the menu for example)
|
||||
|
||||
$other_filters_items = get_items_for_filter('tags');
|
||||
if (false === $other_filters_items)
|
||||
{
|
||||
$filter_tags = get_available_tags();
|
||||
usort($filter_tags, 'tag_alpha_compare');
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_tags = get_common_tags($other_filters_items, 0);
|
||||
|
||||
// the user may have started a search on 2 or more tags that have no
|
||||
// intersection. In this case, $search_items is empty and get_common_tags
|
||||
// returns nothing. We should still display the list of selected tags. We
|
||||
// have to "force" them in the list.
|
||||
$missing_tag_ids = array_diff($my_search['fields']['tags']['words'], array_column($filter_tags, 'id'));
|
||||
|
||||
if (count($missing_tag_ids) > 0)
|
||||
{
|
||||
$filter_tags = array_merge(get_available_tags($missing_tag_ids), $filter_tags);
|
||||
}
|
||||
}
|
||||
|
||||
$template->assign('TAGS', $filter_tags);
|
||||
|
||||
$filter_tag_ids = count($filter_tags) > 0 ? array_column($filter_tags, 'id') : array();
|
||||
|
||||
// in case the search has forbidden tags for current user, we need to filter the search rule
|
||||
$my_search['fields']['tags']['words'] = array_intersect($my_search['fields']['tags']['words'], $filter_tag_ids);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['tags']) and !($display_filters['tags']['access']))
|
||||
{
|
||||
unset($my_search['fields']['tags']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['expert']))
|
||||
{
|
||||
if (!$display_filters['expert']['access'])
|
||||
{
|
||||
unset($my_search['fields']['expert']);
|
||||
}
|
||||
else
|
||||
{
|
||||
load_language('help_quick_search.lang');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['author']) and $display_filters['author']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('author');
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
author,
|
||||
COUNT(DISTINCT(id)) AS counter
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
AND author IS NOT NULL
|
||||
GROUP BY author
|
||||
;';
|
||||
|
||||
if (!preg_match('/^image_id IN/', $filter_clause))
|
||||
{
|
||||
// we use persistent_cache only for fetching lines filtered only by permissions
|
||||
$cache_key = $persistent_cache->make_key('filter_author_rows'.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $filter_rows))
|
||||
{
|
||||
$filter_rows = query2array($query);
|
||||
$persistent_cache->set($cache_key, $filter_rows);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_rows = query2array($query);
|
||||
}
|
||||
|
||||
$author_names = array();
|
||||
foreach ($filter_rows as $author)
|
||||
{
|
||||
$author_names[] = $author['author'];
|
||||
}
|
||||
$template->assign('AUTHORS', $filter_rows);
|
||||
|
||||
// in case the search has forbidden authors for current user, we need to filter the search rule
|
||||
$my_search['fields']['author']['words'] = array_intersect($my_search['fields']['author']['words'], $author_names);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['author']) and !($display_filters['author']['access']))
|
||||
{
|
||||
unset($my_search['fields']['author']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['date_posted']) and $display_filters['post_date']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('date_posted');
|
||||
$cache_key = $persistent_cache->make_key('filter_date_posted'.$user['id'].$user['cache_update_time']);
|
||||
$set_persistent_cache = !preg_match('/^image_id IN/', $filter_clause) and !$persistent_cache->get($cache_key, $date_posted);
|
||||
|
||||
if (!isset($date_posted))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
SUBDATE(NOW(), INTERVAL 24 HOUR) AS 24h,
|
||||
SUBDATE(NOW(), INTERVAL 7 DAY) AS 7d,
|
||||
SUBDATE(NOW(), INTERVAL 30 DAY) AS 30d,
|
||||
SUBDATE(NOW(), INTERVAL 3 MONTH) AS 3m,
|
||||
SUBDATE(NOW(), INTERVAL 6 MONTH) AS 6m
|
||||
;';
|
||||
$thresholds = query2array($query)[0];
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT id,
|
||||
date_available as date
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
;';
|
||||
|
||||
$list_of_dates = array();
|
||||
$pre_counters = array();
|
||||
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
foreach ($thresholds as $threshold => $date_limit)
|
||||
{
|
||||
if ($row['date'] > $date_limit)
|
||||
{
|
||||
@$pre_counters[$threshold]++;
|
||||
}
|
||||
}
|
||||
|
||||
list($date_without_time) = explode(' ', $row['date']);
|
||||
list($y, $m) = explode('-', $date_without_time);
|
||||
|
||||
@$list_of_dates[$y]['months'][$y.'-'.$m]['days'][$date_without_time]['count']++;
|
||||
@$list_of_dates[$y]['months'][$y.'-'.$m]['count']++;
|
||||
@$list_of_dates[$y]['count']++;
|
||||
}
|
||||
|
||||
$date_posted = array(
|
||||
'pre_counters' => $pre_counters,
|
||||
'list_of_dates' => $list_of_dates,
|
||||
);
|
||||
|
||||
if ($set_persistent_cache)
|
||||
{
|
||||
// for this filter, we do not store in cache the $filter_rows : for a big gallery it may
|
||||
// take more than 10MB. It is smarter to store in cache the result of the computation,
|
||||
// which is just around 100 bytes.
|
||||
$persistent_cache->set($cache_key, $date_posted);
|
||||
}
|
||||
}
|
||||
|
||||
$label_for_threshold = array(
|
||||
'24h' => l10n('last 24 hours'),
|
||||
'7d' => l10n('last 7 days'),
|
||||
'30d' => l10n('last 30 days'),
|
||||
'3m' => l10n('last 3 months'),
|
||||
'6m' => l10n('last 6 months'),
|
||||
);
|
||||
|
||||
$counters = array();
|
||||
foreach (array_keys($label_for_threshold) as $threshold)
|
||||
{
|
||||
$counters[$threshold] = array(
|
||||
'label' => $label_for_threshold[$threshold],
|
||||
'counter' => $date_posted['pre_counters'][$threshold] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
foreach (array_keys($date_posted['list_of_dates']) as $y)
|
||||
{
|
||||
$date_posted['list_of_dates'][$y]['label'] = l10n('year %d', $y);
|
||||
|
||||
foreach (array_keys($date_posted['list_of_dates'][$y]['months']) as $ym)
|
||||
{
|
||||
list(,$m) = explode('-', $ym);
|
||||
$date_posted['list_of_dates'][$y]['months'][$ym]['label'] = $lang['month'][(int)$m].' '.$y;
|
||||
|
||||
foreach (array_keys($date_posted['list_of_dates'][$y]['months'][$ym]['days']) as $ymd)
|
||||
{
|
||||
list(,,$d) = explode('-', $ymd);
|
||||
$date_posted['list_of_dates'][$y]['months'][$ym]['days'][$ymd]['label'] = format_date($ymd);
|
||||
}
|
||||
}
|
||||
}
|
||||
krsort($date_posted['list_of_dates']);
|
||||
|
||||
$template->assign('LIST_DATE_POSTED', $date_posted['list_of_dates']);
|
||||
$template->assign('DATE_POSTED', $counters);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['date_posted']) and !($display_filters['post_date']['access']))
|
||||
{
|
||||
unset($my_search['fields']['date_posted']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['date_created']) and $display_filters['creation_date']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('date_created');
|
||||
$cache_key = $persistent_cache->make_key('filter_date_created'.$user['id'].$user['cache_update_time']);
|
||||
$set_persistent_cache = !preg_match('/^image_id IN/', $filter_clause) and !$persistent_cache->get($cache_key, $date_created);
|
||||
|
||||
if (!isset($date_created))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
SUBDATE(NOW(), INTERVAL 7 DAY) AS 7d,
|
||||
SUBDATE(NOW(), INTERVAL 30 DAY) AS 30d,
|
||||
SUBDATE(NOW(), INTERVAL 3 MONTH) AS 3m,
|
||||
SUBDATE(NOW(), INTERVAL 6 MONTH) AS 6m,
|
||||
SUBDATE(NOW(), INTERVAL 12 MONTH) AS 12m
|
||||
;';
|
||||
$thresholds = query2array($query)[0];
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT id,
|
||||
date_creation as date
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
;';
|
||||
|
||||
$list_of_dates = array();
|
||||
$pre_counters = array();
|
||||
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
if(!empty($row['date'])){
|
||||
foreach ($thresholds as $threshold => $date_limit)
|
||||
{
|
||||
if ($row['date'] > $date_limit)
|
||||
{
|
||||
@$pre_counters[$threshold]++;
|
||||
}
|
||||
}
|
||||
|
||||
list($date_without_time) = explode(' ', $row['date']);
|
||||
list($y, $m) = explode('-', $date_without_time);
|
||||
|
||||
@$list_of_dates[$y]['months'][$y.'-'.$m]['days'][$date_without_time]['count']++;
|
||||
@$list_of_dates[$y]['months'][$y.'-'.$m]['count']++;
|
||||
@$list_of_dates[$y]['count']++;
|
||||
}
|
||||
}
|
||||
|
||||
$date_created = array(
|
||||
'pre_counters' => $pre_counters,
|
||||
'list_of_dates' => $list_of_dates,
|
||||
);
|
||||
|
||||
if ($set_persistent_cache)
|
||||
{
|
||||
// for this filter, we do not store in cache the $filter_rows : for a big gallery it may
|
||||
// take more than 10MB. It is smarter to store in cache the result of the computation,
|
||||
// which is just around 100 bytes.
|
||||
$persistent_cache->set($cache_key, $date_created);
|
||||
}
|
||||
}
|
||||
|
||||
$label_for_threshold = array(
|
||||
'7d' => l10n('last 7 days'),
|
||||
'30d' => l10n('last 30 days'),
|
||||
'3m' => l10n('last 3 months'),
|
||||
'6m' => l10n('last 6 months'),
|
||||
'12m' => l10n('last 12 months'),
|
||||
);
|
||||
|
||||
$counters = array();
|
||||
foreach (array_keys($label_for_threshold) as $threshold)
|
||||
{
|
||||
$counters[$threshold] = array(
|
||||
'label' => $label_for_threshold[$threshold],
|
||||
'counter' => $date_created['pre_counters'][$threshold] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
foreach (array_keys($date_created['list_of_dates']) as $y)
|
||||
{
|
||||
$date_created['list_of_dates'][$y]['label'] = l10n('year %d', $y);
|
||||
|
||||
foreach (array_keys($date_created['list_of_dates'][$y]['months']) as $ym)
|
||||
{
|
||||
list(,$m) = explode('-', $ym);
|
||||
$date_created['list_of_dates'][$y]['months'][$ym]['label'] = $lang['month'][(int)$m].' '.$y;
|
||||
|
||||
foreach (array_keys($date_created['list_of_dates'][$y]['months'][$ym]['days']) as $ymd)
|
||||
{
|
||||
list(,,$d) = explode('-', $ymd);
|
||||
$date_created['list_of_dates'][$y]['months'][$ym]['days'][$ymd]['label'] = format_date($ymd);
|
||||
}
|
||||
}
|
||||
}
|
||||
krsort($date_created['list_of_dates']);
|
||||
|
||||
$template->assign('LIST_DATE_CREATED', $date_created['list_of_dates']);
|
||||
$template->assign('DATE_CREATED', $counters);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['date_created']) and !($display_filters['creation_date']['access']))
|
||||
{
|
||||
unset($my_search['fields']['date_created']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['added_by']) and $display_filters['added_by']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('added_by');
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
COUNT(DISTINCT(id)) AS counter,
|
||||
added_by AS added_by_id
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
GROUP BY added_by_id
|
||||
ORDER BY counter DESC
|
||||
;';
|
||||
|
||||
if (!preg_match('/^image_id IN/', $filter_clause))
|
||||
{
|
||||
// we use persistent_cache only for fetching lines filtered only by permissions
|
||||
$cache_key = $persistent_cache->make_key('filter_added_by_rows'.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $filter_rows))
|
||||
{
|
||||
$filter_rows = query2array($query);
|
||||
$persistent_cache->set($cache_key, $filter_rows);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_rows = query2array($query);
|
||||
}
|
||||
|
||||
$added_by = $filter_rows;
|
||||
$user_ids = array();
|
||||
|
||||
if (count($added_by) > 0)
|
||||
{
|
||||
// now let's find the usernames of added_by users
|
||||
foreach ($added_by as $i)
|
||||
{
|
||||
$user_ids[] = $i['added_by_id'];
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
'.$conf['user_fields']['id'].' AS id,
|
||||
'.$conf['user_fields']['username'].' AS username
|
||||
FROM '.USERS_TABLE.'
|
||||
WHERE '.$conf['user_fields']['id'].' IN ('.implode(',', $user_ids).')
|
||||
;';
|
||||
$username_of = query2array($query, 'id', 'username');
|
||||
|
||||
foreach (array_keys($added_by) as $added_by_idx)
|
||||
{
|
||||
$added_by_id = $added_by[$added_by_idx]['added_by_id'];
|
||||
$added_by[$added_by_idx]['added_by_name'] = $username_of[$added_by_id] ?? 'user #'.$added_by_id.' (deleted)';
|
||||
}
|
||||
}
|
||||
|
||||
$template->assign('ADDED_BY', $added_by);
|
||||
|
||||
// in case the search has forbidden added_by users for current user, we need to filter the search rule
|
||||
$my_search['fields']['added_by'] = array_intersect($my_search['fields']['added_by'], $user_ids);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['added_by']) and !($display_filters['added_by']['access']))
|
||||
{
|
||||
unset($my_search['fields']['added_by']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['cat']) and $display_filters['album']['access'])
|
||||
{
|
||||
if (!empty($my_search['fields']['cat']['words']))
|
||||
{
|
||||
$fullname_of = array();
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
id,
|
||||
uppercats
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' ON id = cat_id AND user_id = '.$user['id'].'
|
||||
WHERE id IN ('.implode(',', $my_search['fields']['cat']['words']).')
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
$cat_display_name = get_cat_display_name_cache(
|
||||
$row['uppercats'],
|
||||
'admin.php?page=album-' // TODO not sure it's relevant to link to admin pages
|
||||
);
|
||||
$row['fullname'] = strip_tags($cat_display_name);
|
||||
|
||||
$fullname_of[$row['id']] = $row['fullname'];
|
||||
}
|
||||
|
||||
$template->assign('fullname_of', json_encode($fullname_of));
|
||||
|
||||
// in case the search has forbidden albums for current user, we need to filter the search rule
|
||||
$my_search['fields']['cat']['words'] = array_intersect($my_search['fields']['cat']['words'], array_keys($fullname_of));
|
||||
}
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['cat']) and !($display_filters['album']['access']))
|
||||
{
|
||||
unset($my_search['fields']['cat']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['filetypes']) and $display_filters['file_type']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('filetypes');
|
||||
|
||||
// get all file extensions for this user in the gallery, whatever the current filters
|
||||
$cache_key = $persistent_cache->make_key('file_exts'.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $all_exts))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
SUBSTRING_INDEX(path, ".", -1) AS ext,
|
||||
COUNT(DISTINCT(id)) AS counter
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE 1=1'.$page['search_details']['forbidden'].'
|
||||
GROUP BY ext
|
||||
ORDER BY counter DESC
|
||||
;';
|
||||
$all_exts = query2array($query, 'ext', 'counter');
|
||||
$persistent_cache->set($cache_key, $all_exts);
|
||||
}
|
||||
|
||||
if (preg_match('/^image_id IN/', $filter_clause))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
SUBSTRING_INDEX(path, ".", -1) AS ext,
|
||||
COUNT(DISTINCT(id)) AS counter
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
GROUP BY ext
|
||||
ORDER BY counter DESC
|
||||
;';
|
||||
$filtered_exts = query2array($query, 'ext', 'counter');
|
||||
|
||||
$exts = array();
|
||||
foreach ($all_exts as $ext => $counter)
|
||||
{
|
||||
$exts[$ext] = $filtered_exts[$ext] ?? 0;
|
||||
}
|
||||
|
||||
$template->assign('FILETYPES', $exts);
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->assign('FILETYPES', $all_exts);
|
||||
}
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['filetypes']) and !($display_filters['file_type']['access']))
|
||||
{
|
||||
unset($my_search['fields']['filetypes']);
|
||||
}
|
||||
|
||||
// For rating
|
||||
if ($conf['rate'])
|
||||
{
|
||||
$template->assign('SHOW_FILTER_RATINGS', true);
|
||||
|
||||
if (isset($my_search['fields']['ratings']) and $display_filters['rating']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('ratings');
|
||||
|
||||
$cache_key = $persistent_cache->make_key('filter_ratings'.$user['id'].$user['cache_update_time']);
|
||||
|
||||
$set_persistent_cache = !preg_match('/^image_id IN/', $filter_clause) and !$persistent_cache->get($cache_key, $ratings);
|
||||
|
||||
if (!isset($ratings))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT id,
|
||||
rating_score
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause;
|
||||
|
||||
$filter_rows = query2array($query);
|
||||
|
||||
$ratings = array_fill(0, 6, 0);
|
||||
|
||||
foreach ($filter_rows as $row)
|
||||
{
|
||||
$r = 5;
|
||||
|
||||
if (!isset($row['rating_score']))
|
||||
{
|
||||
$r = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for ($i=1; $i<=4; $i++)
|
||||
{
|
||||
if ($row['rating_score'] < $i)
|
||||
{
|
||||
$r = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ratings[$r]++;
|
||||
}
|
||||
|
||||
if ($set_persistent_cache)
|
||||
{
|
||||
// for this filter, we do not store in cache the $filter_rows : for a big gallery it may
|
||||
// take more than 10MB. It is smarter to store in cache the result of the computation,
|
||||
// which is just around 100 bytes.
|
||||
$persistent_cache->set($cache_key, $ratings);
|
||||
}
|
||||
}
|
||||
$template->assign('RATING', $ratings);
|
||||
}
|
||||
else if (isset($my_search['fields']['ratings']) and !($display_filters['rating']['access']))
|
||||
{
|
||||
unset($my_search['fields']['ratings']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->assign('SHOW_FILTER_RATINGS', false);
|
||||
if (isset($my_search['fields']['ratings']))
|
||||
{
|
||||
unset($my_search['fields']['ratings']);
|
||||
}
|
||||
}
|
||||
|
||||
// For filesize
|
||||
if (isset($my_search['fields']['filesize_min']) && isset($my_search['fields']['filesize_max']) and $display_filters['file_size']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('filesize');
|
||||
|
||||
$filesizes = array();
|
||||
$filesize = array();
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT id,
|
||||
filesize
|
||||
FROM '.IMAGES_TABLE.' AS i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
;';
|
||||
$result = pwg_query($query);
|
||||
while ($row = pwg_db_fetch_assoc($result))
|
||||
{
|
||||
@$filesizes[sprintf('%.1f', $row['filesize']/1024)]++;
|
||||
}
|
||||
|
||||
if (empty($filesizes))
|
||||
{ // arbitrary values, only used when no photos on the gallery
|
||||
$filesizes = array(0, 1, 2, 5, 8, 15);
|
||||
}
|
||||
|
||||
$unique_filesizes = array_keys($filesizes);
|
||||
sort($unique_filesizes, SORT_NUMERIC);
|
||||
|
||||
$filesize['list'] = implode(',', $unique_filesizes);
|
||||
|
||||
$filesize['bounds'] = array(
|
||||
'min' => $unique_filesizes[0],
|
||||
'max' => end($unique_filesizes),
|
||||
);
|
||||
|
||||
// warning: we will (hopefully) have smarter values for filters. The min/max of the
|
||||
// current search won't always be the first/last values found. It's going to be a
|
||||
// problem with this way to select selected values
|
||||
$filesize['selected'] = array(
|
||||
'min' => !empty($my_search['fields']['filesize_min']) ? sprintf('%.1f', $my_search['fields']['filesize_min']/1024) : $unique_filesizes[0],
|
||||
'max' => !empty($my_search['fields']['filesize_max']) ? sprintf('%.1f', $my_search['fields']['filesize_max']/1024) : end($unique_filesizes),
|
||||
);
|
||||
|
||||
$template->assign('FILESIZE', $filesize );
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['filesize_min']) && isset($my_search['fields']['filesize_max']) and !($display_filters['file_size']['access']))
|
||||
{
|
||||
unset($my_search['fields']['filesize_min']);
|
||||
unset($my_search['fields']['filesize_max']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['ratios']) and $display_filters['ratio']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('ratios');
|
||||
|
||||
$cache_key = $persistent_cache->make_key('filter_ratios'.$user['id'].$user['cache_update_time']);
|
||||
|
||||
$set_persistent_cache = !preg_match('/^image_id IN/', $filter_clause) and !$persistent_cache->get($cache_key, $ratios);
|
||||
|
||||
if (!isset($ratios))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
DISTINCT id,
|
||||
width,
|
||||
height
|
||||
FROM '.IMAGES_TABLE.' as i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
AND width IS NOT NULL
|
||||
AND height IS NOT NULL
|
||||
;';
|
||||
|
||||
$filter_rows = query2array($query);
|
||||
|
||||
$ratios = array(
|
||||
'Portrait' => 0,
|
||||
'square' => 0,
|
||||
'Landscape' => 0,
|
||||
'Panorama' => 0,
|
||||
);
|
||||
|
||||
foreach ($filter_rows as $row)
|
||||
{
|
||||
if ($row['width'] <= 0 and $row['height'] <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$r = $row['width'] / $row['height'];
|
||||
if ($r < 0.95)
|
||||
{
|
||||
$ratios['Portrait']++;
|
||||
}
|
||||
else if ($r >= 0.95 and $r <= 1.05)
|
||||
{
|
||||
$ratios['square']++;
|
||||
}
|
||||
else if ($r > 1.05 and $r < 2)
|
||||
{
|
||||
$ratios['Landscape']++;
|
||||
}
|
||||
else if ($r >= 2)
|
||||
{
|
||||
$ratios['Panorama']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($set_persistent_cache)
|
||||
{
|
||||
// for this filter, we do not store in cache the $filter_rows : for a big gallery it may
|
||||
// take more than 10MB. It is smarter to store in cache the result of the computation,
|
||||
// which is just around 100 bytes.
|
||||
$persistent_cache->set($cache_key, $ratios);
|
||||
}
|
||||
}
|
||||
$template->assign('RATIOS', $ratios);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['ratios']) and !($display_filters['ratio']['access']))
|
||||
{
|
||||
unset($my_search['fields']['ratios']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['height_min']) and isset($my_search['fields']['height_max']) and $display_filters['height']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('height');
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
height
|
||||
FROM '.IMAGES_TABLE.' as i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
AND height IS NOT NULL
|
||||
GROUP BY height
|
||||
ORDER BY height ASC
|
||||
;';
|
||||
|
||||
if (!preg_match('/^image_id IN/', $filter_clause))
|
||||
{
|
||||
// we use persistent_cache only for fetching lines filtered only by permissions
|
||||
$cache_key = $persistent_cache->make_key('filter_height_rows'.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $filter_rows))
|
||||
{
|
||||
$filter_rows = query2array($query, null, 'height');
|
||||
$persistent_cache->set($cache_key, $filter_rows);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_rows = query2array($query, null, 'height');
|
||||
}
|
||||
|
||||
$heights = $filter_rows;
|
||||
|
||||
$height = array(
|
||||
'list' => implode(',', $heights),
|
||||
'bounds' => array(
|
||||
'min' => $heights[0],
|
||||
'max' => end($heights),
|
||||
),
|
||||
'selected' => array(
|
||||
'min' => !empty($my_search['fields']['height_min']) ? $my_search['fields']['height_min'] : $heights[0],
|
||||
'max' => !empty($my_search['fields']['height_max']) ? $my_search['fields']['height_max'] : end($heights),
|
||||
)
|
||||
);
|
||||
|
||||
$template->assign('HEIGHT', $height);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['height_min']) && isset($my_search['fields']['height_max']) and !($display_filters['height']['access']))
|
||||
{
|
||||
unset($my_search['fields']['height_min']);
|
||||
unset($my_search['fields']['height_max']);
|
||||
}
|
||||
|
||||
if (isset($my_search['fields']['width_min']) and isset($my_search['fields']['width_max']) and $display_filters['width']['access'])
|
||||
{
|
||||
$filter_clause = get_clause_for_filter('width');
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
width
|
||||
FROM '.IMAGES_TABLE.' as i
|
||||
JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id
|
||||
WHERE '.$filter_clause.'
|
||||
AND width IS NOT NULL
|
||||
GROUP BY width
|
||||
ORDER BY width ASC
|
||||
;';
|
||||
|
||||
if (!preg_match('/^image_id IN/', $filter_clause))
|
||||
{
|
||||
// we use persistent_cache only for fetching lines filtered only by permissions
|
||||
$cache_key = $persistent_cache->make_key('filter_width_rows'.$user['id'].$user['cache_update_time']);
|
||||
if (!$persistent_cache->get($cache_key, $filter_rows))
|
||||
{
|
||||
$filter_rows = query2array($query, null, 'width');
|
||||
$persistent_cache->set($cache_key, $filter_rows);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_rows = query2array($query, null, 'width');
|
||||
}
|
||||
|
||||
$widths = $filter_rows;
|
||||
|
||||
$width = array(
|
||||
'list' => implode(',', $widths),
|
||||
'bounds' => array(
|
||||
'min' => $widths[0],
|
||||
'max' => end($widths),
|
||||
),
|
||||
'selected' => array(
|
||||
'min' => !empty($my_search['fields']['width_min']) ? $my_search['fields']['width_min'] : $widths[0],
|
||||
'max' => !empty($my_search['fields']['width_max']) ? $my_search['fields']['width_max'] : end($widths),
|
||||
)
|
||||
);
|
||||
|
||||
$template->assign('WIDTH', $width);
|
||||
}
|
||||
|
||||
else if (isset($my_search['fields']['width_min']) && isset($my_search['fields']['width_max']) and !($display_filters['width']['access']))
|
||||
{
|
||||
unset($my_search['fields']['width_min']);
|
||||
unset($my_search['fields']['width_max']);
|
||||
}
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'GP' => json_encode($my_search),
|
||||
'SEARCH_ID' => $page['search'],
|
||||
)
|
||||
);
|
||||
|
||||
if (0 == $page['start'] and !isset($page['chronology_field']) and isset($page['search_details']))
|
||||
{
|
||||
if (isset($page['search_details']['matching_cat_ids']))
|
||||
{
|
||||
$cat_ids = $page['search_details']['matching_cat_ids'];
|
||||
if (count($cat_ids))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
c.*
|
||||
FROM '.CATEGORIES_TABLE.' AS c
|
||||
INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' ON c.id = cat_id and user_id = '.$user['id'].'
|
||||
WHERE id IN ('.implode(',', $cat_ids).')
|
||||
;';
|
||||
$cats = query2array($query);
|
||||
usort($cats, 'name_compare');
|
||||
$albums_found = array();
|
||||
foreach ($cats as $cat)
|
||||
{
|
||||
$single_link = false;
|
||||
$albums_found[] = get_cat_display_name_cache(
|
||||
$cat['uppercats'],
|
||||
'',
|
||||
$single_link
|
||||
);
|
||||
}
|
||||
|
||||
if (count($albums_found) > 0)
|
||||
{
|
||||
$template->assign('ALBUMS_FOUND', $albums_found);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($page['search_details']['matching_tag_ids']))
|
||||
{
|
||||
$tag_ids = $page['search_details']['matching_tag_ids'];
|
||||
|
||||
if (count($tag_ids) > 0)
|
||||
{
|
||||
$tags = get_available_tags($tag_ids);
|
||||
usort($tags, 'tag_alpha_compare');
|
||||
$tags_found = array();
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$url = make_index_url(
|
||||
array(
|
||||
'tags' => array($tag)
|
||||
)
|
||||
);
|
||||
$tags_found[] = sprintf('<a href="%s">%s</a>', $url, $tag['name']);
|
||||
}
|
||||
|
||||
if (count($tags_found) > 0)
|
||||
{
|
||||
$template->assign('TAGS_FOUND', $tags_found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
/**
|
||||
* This included page checks section related parameter and provides
|
||||
* following informations:
|
||||
*
|
||||
* - $page['title']
|
||||
*
|
||||
* - $page['items']: ordered list of items to display
|
||||
*
|
||||
*/
|
||||
|
||||
// "index.php?/category/12-foo/start-24" or
|
||||
// "index.php/category/12-foo/start-24"
|
||||
// must return :
|
||||
//
|
||||
// array(
|
||||
// 'section' => 'categories',
|
||||
// 'category' => array('id'=>12, ...),
|
||||
// 'start' => 24
|
||||
// );
|
||||
|
||||
|
||||
$page['items'] = array();
|
||||
$page['start'] = $page['startcat'] = 0;
|
||||
|
||||
// some ISPs set PATH_INFO to empty string or to SCRIPT_FILENAME while in the
|
||||
// default apache implementation it is not set
|
||||
if ( $conf['question_mark_in_urls']==false and
|
||||
isset($_SERVER["PATH_INFO"]) and !empty($_SERVER["PATH_INFO"]) )
|
||||
{
|
||||
$rewritten = $_SERVER["PATH_INFO"];
|
||||
$rewritten = str_replace('//', '/', $rewritten);
|
||||
$path_count = count( explode('/', $rewritten) );
|
||||
$page['root_path'] = PHPWG_ROOT_PATH.str_repeat('../', $path_count-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
$rewritten = '';
|
||||
foreach (array_keys($_GET) as $keynum => $key)
|
||||
{
|
||||
$rewritten = $key;
|
||||
break;
|
||||
}
|
||||
|
||||
// the $_GET keys are not protected in include/common.inc.php, only the values
|
||||
$rewritten = pwg_db_real_escape_string($rewritten);
|
||||
$page['root_path'] = PHPWG_ROOT_PATH;
|
||||
}
|
||||
|
||||
if ( strncmp($page['root_path'], './', 2) == 0 )
|
||||
{
|
||||
$page['root_path'] = substr($page['root_path'], 2);
|
||||
}
|
||||
|
||||
$page['section_url'] = $rewritten;
|
||||
|
||||
// deleting first "/" if displayed
|
||||
$tokens = explode('/', ltrim($rewritten, '/') );
|
||||
// $tokens = array(
|
||||
// 0 => category,
|
||||
// 1 => 12-foo,
|
||||
// 2 => start-24
|
||||
// );
|
||||
|
||||
$next_token = 0;
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | picture page |
|
||||
// +-----------------------------------------------------------------------+
|
||||
// the first token must be the identifier for the picture
|
||||
if (script_basename() == 'picture')
|
||||
{
|
||||
$token = $tokens[$next_token];
|
||||
$next_token++;
|
||||
if ( is_numeric($token) )
|
||||
{
|
||||
$page['image_id'] = $token;
|
||||
if ($page['image_id']==0)
|
||||
{
|
||||
bad_request('invalid picture identifier');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
preg_match('/^(\d+-)?(.*)?$/', $token, $matches);
|
||||
if (isset($matches[1]) and is_numeric($matches[1]=rtrim($matches[1],'-')) )
|
||||
{
|
||||
$page['image_id'] = $matches[1];
|
||||
if ( !empty($matches[2]) )
|
||||
{
|
||||
$page['image_file'] = $matches[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['image_id'] = 0; // more work in picture.php
|
||||
if ( !empty($matches[2]) )
|
||||
{
|
||||
$page['image_file'] = $matches[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
bad_request('picture identifier is missing');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$page = array_merge( $page, parse_section_url( $tokens, $next_token) );
|
||||
|
||||
if ( !isset($page['section']) )
|
||||
{
|
||||
$page['section'] = 'categories';
|
||||
|
||||
switch (script_basename())
|
||||
{
|
||||
case 'picture':
|
||||
break;
|
||||
case 'index':
|
||||
{
|
||||
// No section defined, go to random url
|
||||
if ( !empty($conf['random_index_redirect']) and empty($tokens[$next_token]) )
|
||||
{
|
||||
$random_index_redirect = array();
|
||||
foreach ($conf['random_index_redirect'] as $random_url => $random_url_condition)
|
||||
{
|
||||
if (empty($random_url_condition) or eval($random_url_condition))
|
||||
{
|
||||
$random_index_redirect[] = $random_url;
|
||||
}
|
||||
}
|
||||
if (!empty($random_index_redirect))
|
||||
{
|
||||
redirect($random_index_redirect[mt_rand(0, count($random_index_redirect)-1)]);
|
||||
}
|
||||
}
|
||||
$page['is_homepage'] = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
trigger_error('script_basename "'.script_basename().'" unknown',
|
||||
E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
$page = array_merge( $page, parse_well_known_params_url( $tokens, $next_token) );
|
||||
|
||||
//access a picture only by id, file or id-file without given section
|
||||
if ( script_basename()=='picture' and 'categories'==$page['section'] and
|
||||
!isset($page['category']) and !isset($page['chronology_field']) )
|
||||
{
|
||||
$page['flat'] = true;
|
||||
}
|
||||
|
||||
// $page['nb_image_page'] is the number of picture to display on this page
|
||||
// By default, it is the same as the $user['nb_image_page']
|
||||
$page['nb_image_page'] = $user['nb_image_page'];
|
||||
|
||||
// if flat mode is active, we must consider the image set as a standard set
|
||||
// and not as a category set because we can't use the #image_category.rank :
|
||||
// displayed images are not directly linked to the displayed category
|
||||
if ('categories' == $page['section'] and !isset($page['flat']))
|
||||
{
|
||||
$conf['order_by'] = $conf['order_by_inside_category'];
|
||||
}
|
||||
|
||||
if (pwg_get_session_var('image_order',0) > 0)
|
||||
{
|
||||
$image_order_id = pwg_get_session_var('image_order');
|
||||
|
||||
$orders = get_category_preferred_image_orders();
|
||||
|
||||
// the current session stored image_order might be not compatible with
|
||||
// current image set, for example if the current image_order is the rank
|
||||
// and that we are displaying images related to a tag.
|
||||
//
|
||||
// In case of incompatibility, the session stored image_order is removed.
|
||||
if ($orders[$image_order_id][2])
|
||||
{
|
||||
$conf['order_by'] = str_replace(
|
||||
'ORDER BY ',
|
||||
'ORDER BY '.$orders[$image_order_id][1].',',
|
||||
$conf['order_by']
|
||||
);
|
||||
$page['super_order_by'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pwg_unset_session_var('image_order');
|
||||
$page['super_order_by'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$forbidden = get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'category_id',
|
||||
'visible_categories' => 'category_id',
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
'AND'
|
||||
);
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | category |
|
||||
// +-----------------------------------------------------------------------+
|
||||
if ('categories' == $page['section'])
|
||||
{
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$page['title'] = get_combined_categories_content_title();
|
||||
}
|
||||
elseif (isset($page['category']))
|
||||
{
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'comment' => trigger_change(
|
||||
'render_category_description',
|
||||
$page['category']['comment'],
|
||||
'main_page_category_description'
|
||||
),
|
||||
'title' => get_cat_display_name($page['category']['upper_names'], '', false),
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['title'] = ''; // will be set later
|
||||
}
|
||||
|
||||
// GET IMAGES LIST
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$cat_ids = array($page['category']['id']);
|
||||
foreach ($page['combined_categories'] as $category)
|
||||
{
|
||||
$cat_ids[] = $category['id'];
|
||||
}
|
||||
|
||||
$page['items'] = get_image_ids_for_categories($cat_ids);
|
||||
}
|
||||
elseif
|
||||
(
|
||||
$page['startcat'] == 0 and
|
||||
(!isset($page['chronology_field'])) and // otherwise the calendar will requery all subitems
|
||||
(
|
||||
(isset($page['category'])) or
|
||||
(isset($page['flat']))
|
||||
)
|
||||
)
|
||||
{
|
||||
if ( !empty($page['category']['image_order']) and !isset($page['super_order_by']) )
|
||||
{
|
||||
$conf[ 'order_by' ] = ' ORDER BY '.$page['category']['image_order'];
|
||||
}
|
||||
|
||||
// flat categories mode
|
||||
if (isset($page['flat']))
|
||||
{
|
||||
// get all allowed sub-categories
|
||||
if ( isset($page['category']) )
|
||||
{
|
||||
$query = '
|
||||
SELECT id
|
||||
FROM '.CATEGORIES_TABLE.'
|
||||
WHERE
|
||||
uppercats LIKE \''.$page['category']['uppercats'].',%\' '
|
||||
.get_sql_condition_FandF(
|
||||
array(
|
||||
'forbidden_categories' => 'id',
|
||||
'visible_categories' => 'id',
|
||||
),
|
||||
"\n AND"
|
||||
);
|
||||
|
||||
$subcat_ids = query2array($query,null, 'id');
|
||||
$subcat_ids[] = $page['category']['id'];
|
||||
$where_sql = 'category_id IN ('.implode(',',$subcat_ids).')';
|
||||
// remove categories from forbidden because just checked above
|
||||
$forbidden = get_sql_condition_FandF(
|
||||
array( 'visible_images' => 'id' ),
|
||||
'AND'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$cache_key = $persistent_cache->make_key('all_iids'.$user['id'].$user['cache_update_time'].$conf['order_by']);
|
||||
unset($page['is_homepage']);
|
||||
$where_sql = '1=1';
|
||||
}
|
||||
}
|
||||
// normal mode
|
||||
else
|
||||
{
|
||||
$where_sql = 'category_id = '.$page['category']['id'];
|
||||
}
|
||||
|
||||
if ( !isset($cache_key) || !$persistent_cache->get($cache_key, $page['items']))
|
||||
{
|
||||
// main query
|
||||
$query = '
|
||||
SELECT DISTINCT(image_id)
|
||||
FROM '.IMAGE_CATEGORY_TABLE.'
|
||||
INNER JOIN '.IMAGES_TABLE.' ON id = image_id
|
||||
WHERE
|
||||
'.$where_sql.'
|
||||
'.$forbidden.'
|
||||
'.$conf['order_by'].'
|
||||
;';
|
||||
|
||||
$page['items'] = query2array($query,null, 'image_id');
|
||||
|
||||
if ( isset($cache_key) )
|
||||
$persistent_cache->set($cache_key, $page['items']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// special sections
|
||||
else
|
||||
{
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | tags section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
if ($page['section'] == 'tags')
|
||||
{
|
||||
$page['tag_ids'] = array();
|
||||
foreach ($page['tags'] as $tag)
|
||||
{
|
||||
$page['tag_ids'][] = $tag['id'];
|
||||
}
|
||||
|
||||
$items = get_image_ids_for_tags($page['tag_ids']);
|
||||
|
||||
if (count($items) == 0)
|
||||
{
|
||||
$logger->info(
|
||||
'attempt to see the name of the tag #'.implode(', #', $page['tag_ids'])
|
||||
.' from the address : '.$_SERVER['REMOTE_ADDR']
|
||||
);
|
||||
access_denied();
|
||||
}
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => get_tags_content_title(),
|
||||
'items' => $items,
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | search section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'search')
|
||||
{
|
||||
include_once( PHPWG_ROOT_PATH .'include/functions_search.inc.php' );
|
||||
|
||||
$search_result = get_search_results($page['search'], @$page['super_order_by'] );
|
||||
|
||||
//save the details of the query search
|
||||
if ( isset($search_result['qs']) )
|
||||
{
|
||||
$page['qsearch_details'] = $search_result['qs'];
|
||||
}
|
||||
else if (isset($search_result['search_details']))
|
||||
{
|
||||
$page['search_details'] = $search_result['search_details'];
|
||||
}
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'items' => $search_result['items'],
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.l10n('Search results').'</a>',
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | favorite section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'favorites')
|
||||
{
|
||||
check_user_favorites();
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.l10n('Favorites').'</a>'
|
||||
)
|
||||
);
|
||||
|
||||
if (!empty($_GET['action']) && ($_GET['action'] == 'remove_all_from_favorites'))
|
||||
{
|
||||
$query = '
|
||||
DELETE FROM '.FAVORITES_TABLE.'
|
||||
WHERE user_id = '.$user['id'].'
|
||||
;';
|
||||
pwg_query($query);
|
||||
redirect(make_index_url( array('section'=>'favorites') ));
|
||||
}
|
||||
else
|
||||
{
|
||||
$query = '
|
||||
SELECT image_id
|
||||
FROM '.FAVORITES_TABLE.'
|
||||
INNER JOIN '.IMAGES_TABLE.' ON image_id = id
|
||||
WHERE user_id = '.$user['id'].'
|
||||
'.get_sql_condition_FandF(
|
||||
array(
|
||||
'visible_images' => 'id'
|
||||
),
|
||||
'AND'
|
||||
).'
|
||||
'.$conf['order_by'].'
|
||||
;';
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'items' => query2array($query,null, 'image_id'),
|
||||
)
|
||||
);
|
||||
|
||||
if (count($page['items'])>0)
|
||||
{
|
||||
$template->assign(
|
||||
'favorite',
|
||||
array(
|
||||
'U_FAVORITE' => add_url_params(
|
||||
make_index_url( array('section'=>'favorites') ),
|
||||
array('action'=>'remove_all_from_favorites')
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | recent pictures section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'recent_pics')
|
||||
{
|
||||
if ( !isset($page['super_order_by']) )
|
||||
{
|
||||
$conf['order_by'] = str_replace(
|
||||
'ORDER BY ',
|
||||
'ORDER BY date_available DESC,',
|
||||
$conf['order_by']
|
||||
);
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT DISTINCT(id)
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
|
||||
WHERE '
|
||||
.get_recent_photos_sql('date_available').'
|
||||
'.$forbidden
|
||||
.$conf['order_by'].'
|
||||
;';
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.l10n('Recent photos').'</a>',
|
||||
'items' => query2array($query,null, 'id'),
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | recently updated categories section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'recent_cats')
|
||||
{
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.l10n('Recent albums').'</a>'
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | most visited section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'most_visited')
|
||||
{
|
||||
$page['super_order_by'] = true;
|
||||
$conf['order_by'] = ' ORDER BY hit DESC, id DESC';
|
||||
|
||||
$query = '
|
||||
SELECT DISTINCT(id)
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
|
||||
WHERE hit > 0
|
||||
'.$forbidden.'
|
||||
'.$conf['order_by'].'
|
||||
LIMIT '.$conf['top_number'].'
|
||||
;';
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.$conf['top_number'].' '.l10n('Most visited').'</a>',
|
||||
'items' => query2array($query,null, 'id'),
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | best rated section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'best_rated')
|
||||
{
|
||||
$page['super_order_by'] = true;
|
||||
$conf['order_by'] = ' ORDER BY rating_score DESC, id DESC';
|
||||
|
||||
$query ='
|
||||
SELECT DISTINCT(id)
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
|
||||
WHERE rating_score IS NOT NULL
|
||||
'.$forbidden.'
|
||||
'.$conf['order_by'].'
|
||||
LIMIT '.$conf['top_number'].'
|
||||
;';
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.$conf['top_number'].' '.l10n('Best rated').'</a>',
|
||||
'items' => query2array($query,null, 'id'),
|
||||
)
|
||||
);
|
||||
}
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | list section |
|
||||
// +-----------------------------------------------------------------------+
|
||||
else if ($page['section'] == 'list')
|
||||
{
|
||||
$query ='
|
||||
SELECT DISTINCT(id)
|
||||
FROM '.IMAGES_TABLE.'
|
||||
INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
|
||||
WHERE image_id IN ('.implode(',', $page['list']).')
|
||||
'.$forbidden.'
|
||||
'.$conf['order_by'].'
|
||||
;';
|
||||
|
||||
$page = array_merge(
|
||||
$page,
|
||||
array(
|
||||
'title' => '<a href="'.duplicate_index_url(array('start'=>0)).'">'
|
||||
.l10n('Random photos').'</a>',
|
||||
'items' => query2array($query,null, 'id'),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | chronology |
|
||||
// +-----------------------------------------------------------------------+
|
||||
if (isset($page['chronology_field']))
|
||||
{
|
||||
unset($page['is_homepage']);
|
||||
include_once( PHPWG_ROOT_PATH.'include/functions_calendar.inc.php' );
|
||||
initialize_calendar();
|
||||
}
|
||||
|
||||
// title update
|
||||
if (isset($page['title']))
|
||||
{
|
||||
$page['section_title'] = '<a href="'.get_gallery_home_url().'">'.l10n('Home').'</a>';
|
||||
if (!empty($page['title']))
|
||||
{
|
||||
$page['section_title'] .= $conf['level_separator'].$page['title'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$page['title'] = $page['section_title'];
|
||||
}
|
||||
}
|
||||
|
||||
// add meta robots noindex, nofollow to avoid unnecesary robot crawls
|
||||
$page['meta_robots']=array();
|
||||
if ( isset($page['chronology_field'])
|
||||
or ( isset($page['flat']) and isset($page['category']) )
|
||||
or 'list'==$page['section'] or 'recent_pics'==$page['section'] )
|
||||
{
|
||||
$page['meta_robots']=array('noindex'=>1, 'nofollow'=>1);
|
||||
}
|
||||
elseif ('tags'==$page['section'])
|
||||
{
|
||||
if ( count($page['tag_ids'])>1 )
|
||||
{
|
||||
$page['meta_robots']=array('noindex'=>1, 'nofollow'=>1);
|
||||
}
|
||||
}
|
||||
elseif ('recent_cats'==$page['section'])
|
||||
{
|
||||
$page['meta_robots']['noindex']=1;
|
||||
}
|
||||
elseif ('search'==$page['section'])
|
||||
{
|
||||
$page['meta_robots']=array('noindex'=>1, 'nofollow'=>1);
|
||||
}
|
||||
elseif ('categories' == $page['section'])
|
||||
{
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$page['meta_robots'] = array('noindex'=>1, 'nofollow'=>1);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $filter['enabled'] )
|
||||
{
|
||||
$page['meta_robots']['noindex']=1;
|
||||
}
|
||||
|
||||
// see if we need a redirect because of a permalink
|
||||
if ( 'categories'==$page['section'] and isset($page['category']) and !isset($page['combined_categories']))
|
||||
{
|
||||
$need_redirect=false;
|
||||
if ( empty($page['category']['permalink']) )
|
||||
{
|
||||
if ( $conf['category_url_style'] == 'id-name' and
|
||||
@$page['hit_by']['cat_url_name'] !== str2url($page['category']['name']) )
|
||||
{
|
||||
$need_redirect=true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $page['category']['permalink'] !== @$page['hit_by']['cat_permalink'] )
|
||||
{
|
||||
$need_redirect=true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($need_redirect)
|
||||
{
|
||||
check_restrictions($page['category']['id']);
|
||||
$redirect_url = script_basename()=='picture' ? duplicate_picture_url() : duplicate_index_url();
|
||||
|
||||
if (!headers_sent())
|
||||
{ // this is a permanent redirection
|
||||
set_status_header(301);
|
||||
redirect_http( $redirect_url );
|
||||
}
|
||||
redirect( $redirect_url );
|
||||
}
|
||||
unset( $need_redirect, $page['hit_by'] );
|
||||
}
|
||||
|
||||
array_push($page['body_classes'], 'section-'.$page['section']);
|
||||
$page['body_data']['section'] = $page['section'];
|
||||
|
||||
|
||||
if ('categories' == $page['section'] && isset($page['category']))
|
||||
{
|
||||
array_push($page['body_classes'], 'category-'.$page['category']['id']);
|
||||
$page['body_data']['category_id'] = $page['category']['id'];
|
||||
|
||||
if (isset($page['combined_categories']))
|
||||
{
|
||||
$page['body_data']['combined_category_ids'] = array();
|
||||
foreach ($page['combined_categories'] as $combined_categories)
|
||||
{
|
||||
array_push($page['body_classes'],'category-'.$combined_categories['id']);
|
||||
array_push($page['body_data']['combined_category_ids'], $combined_categories['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (isset($page['tags']))
|
||||
{
|
||||
$page['body_data']['tag_ids'] = array();
|
||||
foreach ($page['tags'] as $tag)
|
||||
{
|
||||
array_push($page['body_classes'], 'tag-'.$tag['id']);
|
||||
array_push($page['body_data']['tag_ids'], $tag['id']);
|
||||
}
|
||||
|
||||
}
|
||||
elseif (isset($page['search']))
|
||||
{
|
||||
array_push($page['body_classes'], 'search-'.$page['search']);
|
||||
$page['body_data']['search_id'] = $page['search'];
|
||||
}
|
||||
|
||||
if (isset($page['image_id']))
|
||||
{
|
||||
array_push($page['body_classes'], 'image-'.$page['image_id']);
|
||||
$page['body_data']['image_id'] = $page['image_id'];
|
||||
}
|
||||
|
||||
trigger_notify('loc_end_section_init');
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
global $page;
|
||||
|
||||
$selected_related_tags_info = array();
|
||||
|
||||
foreach ($page['tags'] as $key=>$tag)
|
||||
{
|
||||
$other_tags = $page['tags'];
|
||||
unset($other_tags[$key]);
|
||||
|
||||
$selected_related_tags_info[$key] =
|
||||
array(
|
||||
'tag_name' => trigger_change('render_tag_name', $page['tags'][$key]['name'], $page['tags'][$key]),
|
||||
'item_count' => '',
|
||||
'index_url' => make_index_url(
|
||||
array(
|
||||
'tags' => array( $page['tags'][$key] )
|
||||
)
|
||||
),
|
||||
'remove_url' => make_index_url(
|
||||
array(
|
||||
'tags' => $other_tags
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$template->assign(
|
||||
array(
|
||||
'SELECT_RELATED_TAGS' => $selected_related_tags_info,
|
||||
)
|
||||
);
|
||||
|
||||
$template->set_filename('selected_tags', 'include/selected_tags.inc.tpl');
|
||||
$template->assign_var_from_handle('SELECTED_TAGS_TEMPLATE', 'selected_tags');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
||||
Smarty: the PHP compiling template engine
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU Lesser General Public License below for more details.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Smarty template engine
|
||||
Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic.
|
||||
|
||||

|
||||
|
||||
## Documentation
|
||||
Read the [documentation](https://smarty-php.github.io/smarty/) to find out how to use it.
|
||||
|
||||
## Requirements
|
||||
Smarty v5 can be run with PHP 7.2 to PHP 8.4.
|
||||
|
||||
## Installation
|
||||
Smarty versions 3.1.11 or later can be installed with [Composer](https://getcomposer.org/).
|
||||
|
||||
To get the latest stable version of Smarty use:
|
||||
```bash
|
||||
composer require smarty/smarty
|
||||
````
|
||||
|
||||
More in the [Getting Started](./docs/getting-started.md) section of the docs.
|
||||
|
||||
## Sponsors
|
||||
|
||||
Smarty is sponsored by:
|
||||
- Marc Laporte [@marclaporte](https://github.com/marclaporte)
|
||||
- [Temma](https://github.com/Digicreon/Temma), the MVC framework based on Smarty
|
||||
|
||||
Thank you!
|
||||
@@ -0,0 +1,20 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Smarty currently supports the latest minor version of Smarty 4 and Smarty 5.
|
||||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| 5.0.x | :white_check_mark: |
|
||||
| 4.3.x | :white_check_mark: |
|
||||
| < 4.3 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have discovered a security issue with Smarty, please contact us at mail [at] simonwisselink.nl. Do not
|
||||
disclose your findings publicly and **PLEASE** do not file an Issue (because that would disclose your findings
|
||||
publicly.)
|
||||
|
||||
We will try to confirm the vulnerability and develop a fix if appropriate. When we release the fix, we will publish
|
||||
a security release. Please let us know if you want to be credited.
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// This is a stub PSR-4 loading script that gets all the pieces of //
|
||||
// Smarty 5.x loaded without requiring the use of composer. It's //
|
||||
// not really a 'class' file, but the name is used so we're //
|
||||
// backwards compatible with previous versions of Smarty. //
|
||||
// //
|
||||
// Example: //
|
||||
// require_once("/path/to/smarty/libs/Smarty.class.php"); //
|
||||
// //
|
||||
// $smarty = new Smarty\Smarty; //
|
||||
// $smarty->testInstall(); //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
define('__SMARTY_DIR', __DIR__ . '/../src/');
|
||||
|
||||
// Global function declarations
|
||||
require_once(__SMARTY_DIR . "/functions.php");
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
// Class prefix
|
||||
$prefix = 'Smarty\\';
|
||||
|
||||
// Does the class use the namespace prefix?
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
// If not, move to the next registered autoloader
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack off the prefix part
|
||||
$relative_class = substr($class, $len);
|
||||
|
||||
// Build a path to the include file
|
||||
$file = __SMARTY_DIR . str_replace('\\', '/', $relative_class) . '.php';
|
||||
|
||||
// If the file exists, require it
|
||||
if (file_exists($file)) {
|
||||
require_once($file);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\BlockHandler;
|
||||
|
||||
use Smarty\Template;
|
||||
|
||||
abstract class Base implements BlockHandlerInterface {
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $cacheable = true;
|
||||
|
||||
abstract public function handle($params, $content, Template $template, &$repeat);
|
||||
|
||||
public function isCacheable(): bool {
|
||||
return $this->cacheable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\BlockHandler;
|
||||
|
||||
use Smarty\Template;
|
||||
|
||||
interface BlockHandlerInterface {
|
||||
public function handle($params, $content, Template $template, &$repeat);
|
||||
public function isCacheable(): bool;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\BlockHandler;
|
||||
|
||||
use Smarty\Template;
|
||||
|
||||
class BlockPluginWrapper extends Base {
|
||||
|
||||
private $callback;
|
||||
|
||||
public function __construct($callback, bool $cacheable = true) {
|
||||
$this->callback = $callback;
|
||||
$this->cacheable = $cacheable;
|
||||
}
|
||||
|
||||
public function handle($params, $content, Template $template, &$repeat) {
|
||||
return \call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\BlockHandler;
|
||||
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
|
||||
/**
|
||||
* Smarty {textformat}{/textformat} block plugin
|
||||
* Type: block function
|
||||
* Name: textformat
|
||||
* Purpose: format text a certain way with preset styles
|
||||
* or custom wrap/indent settings
|
||||
* Params:
|
||||
*
|
||||
* - style - string (email)
|
||||
* - indent - integer (0)
|
||||
* - wrap - integer (80)
|
||||
* - wrap_char - string ("\n")
|
||||
* - indent_char - string (" ")
|
||||
* - wrap_boundary - boolean (true)
|
||||
*
|
||||
* @param array $params parameters
|
||||
* @param string $content contents of the block
|
||||
* @param Template $template template object
|
||||
* @param boolean &$repeat repeat flag
|
||||
*
|
||||
* @return string content re-formatted
|
||||
* @author Monte Ohrt <monte at ohrt dot com>
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
class TextFormat implements BlockHandlerInterface {
|
||||
|
||||
public function handle($params, $content, Template $template, &$repeat) {
|
||||
if (is_null($content)) {
|
||||
return;
|
||||
}
|
||||
$style = null;
|
||||
$indent = 0;
|
||||
$indent_first = 0;
|
||||
$indent_char = ' ';
|
||||
$wrap = 80;
|
||||
$wrap_char = "\n";
|
||||
$wrap_cut = false;
|
||||
$assign = null;
|
||||
foreach ($params as $_key => $_val) {
|
||||
switch ($_key) {
|
||||
case 'style':
|
||||
case 'indent_char':
|
||||
case 'wrap_char':
|
||||
case 'assign':
|
||||
$$_key = (string)$_val;
|
||||
break;
|
||||
case 'indent':
|
||||
case 'indent_first':
|
||||
case 'wrap':
|
||||
$$_key = (int)$_val;
|
||||
break;
|
||||
case 'wrap_cut':
|
||||
$$_key = (bool)$_val;
|
||||
break;
|
||||
default:
|
||||
trigger_error("textformat: unknown attribute '{$_key}'");
|
||||
}
|
||||
}
|
||||
if ($style === 'email') {
|
||||
$wrap = 72;
|
||||
}
|
||||
// split into paragraphs
|
||||
$_paragraphs = preg_split('![\r\n]{2}!', $content);
|
||||
foreach ($_paragraphs as &$_paragraph) {
|
||||
if (!$_paragraph) {
|
||||
continue;
|
||||
}
|
||||
// convert mult. spaces & special chars to single space
|
||||
$_paragraph =
|
||||
preg_replace(
|
||||
array(
|
||||
'!\s+!' . Smarty::$_UTF8_MODIFIER,
|
||||
'!(^\s+)|(\s+$)!' . Smarty::$_UTF8_MODIFIER
|
||||
),
|
||||
array(
|
||||
' ',
|
||||
''
|
||||
),
|
||||
$_paragraph
|
||||
);
|
||||
// indent first line
|
||||
if ($indent_first > 0) {
|
||||
$_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph;
|
||||
}
|
||||
// wordwrap sentences
|
||||
$_paragraph = smarty_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut);
|
||||
// indent lines
|
||||
if ($indent > 0) {
|
||||
$_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph);
|
||||
}
|
||||
}
|
||||
$_output = implode($wrap_char . $wrap_char, $_paragraphs);
|
||||
if ($assign) {
|
||||
$template->assign($assign, $_output);
|
||||
} else {
|
||||
return $_output;
|
||||
}
|
||||
}
|
||||
|
||||
public function isCacheable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\Cacheresource;
|
||||
|
||||
use Smarty\Exception;
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
use Smarty\Template\Cached;
|
||||
|
||||
/**
|
||||
* Cache Handler API
|
||||
* @author Rodney Rehm
|
||||
*/
|
||||
abstract class Base
|
||||
{
|
||||
|
||||
/**
|
||||
* populate Cached Object with metadata from Resource
|
||||
*
|
||||
* @param Cached $cached cached object
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function populate(Cached $cached, Template $_template);
|
||||
|
||||
/**
|
||||
* populate Cached Object with timestamp and exists from Resource
|
||||
*
|
||||
* @param Cached $cached
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function populateTimestamp(Cached $cached);
|
||||
|
||||
/**
|
||||
* Read the cached template and process header
|
||||
*
|
||||
* @param Template $_template template object
|
||||
* @param Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
abstract public function process(
|
||||
Template $_template,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Write the rendered template output to cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
* @param string $content content to cache
|
||||
*
|
||||
* @return boolean success
|
||||
*/
|
||||
abstract public function storeCachedContent(Template $_template, $content);
|
||||
|
||||
/**
|
||||
* Read cached template from cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return string content
|
||||
*/
|
||||
abstract public function retrieveCachedContent(Template $_template);
|
||||
|
||||
/**
|
||||
* Empty cache
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param integer $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return integer number of cache files deleted
|
||||
*/
|
||||
abstract public function clearAll(Smarty $smarty, $exp_time = null);
|
||||
|
||||
/**
|
||||
* Empty cache for a specific template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param integer $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return integer number of cache files deleted
|
||||
*/
|
||||
abstract public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time);
|
||||
|
||||
/**
|
||||
* @param Smarty $smarty
|
||||
* @param Cached $cached
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function locked(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
// theoretically locking_timeout should be checked against time_limit (max_execution_time)
|
||||
$start = microtime(true);
|
||||
$hadLock = null;
|
||||
while ($this->hasLock($smarty, $cached)) {
|
||||
$hadLock = true;
|
||||
if (microtime(true) - $start > $smarty->locking_timeout) {
|
||||
// abort waiting for lock release
|
||||
return false;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
return $hadLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is cache is locked for this template
|
||||
*
|
||||
* @param Smarty $smarty
|
||||
* @param Cached $cached
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
// check if lock exists
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty
|
||||
* @param Cached $cached
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acquireLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
// create lock
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty
|
||||
* @param Cached $cached
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function releaseLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
// release lock
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\Cacheresource;
|
||||
|
||||
/**
|
||||
* Smarty Internal Plugin
|
||||
*
|
||||
|
||||
|
||||
*/
|
||||
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
use Smarty\Template\Cached;
|
||||
|
||||
/**
|
||||
* Cache Handler API
|
||||
*
|
||||
|
||||
|
||||
* @author Rodney Rehm
|
||||
*/
|
||||
abstract class Custom extends Base
|
||||
{
|
||||
/**
|
||||
* fetch cached content and its modification time from data source
|
||||
*
|
||||
* @param string $id unique cache content identifier
|
||||
* @param string $name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param string $content cached content
|
||||
* @param integer $mtime cache modification timestamp (epoch)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function fetch($id, $name, $cache_id, $compile_id, &$content, &$mtime);
|
||||
|
||||
/**
|
||||
* Fetch cached content's modification timestamp from data source
|
||||
* {@internal implementing this method is optional.
|
||||
* Only implement it if modification times can be accessed faster than loading the complete cached content.}}
|
||||
*
|
||||
* @param string $id unique cache content identifier
|
||||
* @param string $name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
*
|
||||
* @return integer|boolean timestamp (epoch) the template was modified, or false if not found
|
||||
*/
|
||||
protected function fetchTimestamp($id, $name, $cache_id, $compile_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save content to cache
|
||||
*
|
||||
* @param string $id unique cache content identifier
|
||||
* @param string $name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param integer|null $exp_time seconds till expiration or null
|
||||
* @param string $content content to cache
|
||||
*
|
||||
* @return boolean success
|
||||
*/
|
||||
abstract protected function save($id, $name, $cache_id, $compile_id, $exp_time, $content);
|
||||
|
||||
/**
|
||||
* Delete content from cache
|
||||
*
|
||||
* @param string|null $name template name
|
||||
* @param string|null $cache_id cache id
|
||||
* @param string|null $compile_id compile id
|
||||
* @param integer|null $exp_time seconds till expiration time in seconds or null
|
||||
*
|
||||
* @return integer number of deleted caches
|
||||
*/
|
||||
abstract protected function delete($name, $cache_id, $compile_id, $exp_time);
|
||||
|
||||
/**
|
||||
* populate Cached Object with metadata from Resource
|
||||
*
|
||||
* @param \Smarty\Template\Cached $cached cached object
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(\Smarty\Template\Cached $cached, Template $_template)
|
||||
{
|
||||
$_cache_id = isset($cached->cache_id) ? preg_replace('![^\w\|]+!', '_', $cached->cache_id) : null;
|
||||
$_compile_id = isset($cached->compile_id) ? preg_replace('![^\w]+!', '_', $cached->compile_id) : null;
|
||||
$path = $cached->getSource()->uid . $_cache_id . $_compile_id;
|
||||
$cached->filepath = sha1($path);
|
||||
if ($_template->getSmarty()->cache_locking) {
|
||||
$cached->lock_id = sha1('lock.' . $path);
|
||||
}
|
||||
$this->populateTimestamp($cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* populate Cached Object with timestamp and exists from Resource
|
||||
*
|
||||
* @param \Smarty\Template\Cached $cached
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populateTimestamp(\Smarty\Template\Cached $cached)
|
||||
{
|
||||
$mtime =
|
||||
$this->fetchTimestamp($cached->filepath, $cached->getSource()->name, $cached->cache_id, $cached->compile_id);
|
||||
if ($mtime !== null) {
|
||||
$cached->timestamp = $mtime;
|
||||
$cached->exists = !!$cached->timestamp;
|
||||
return;
|
||||
}
|
||||
$timestamp = null;
|
||||
$this->fetch(
|
||||
$cached->filepath,
|
||||
$cached->getSource()->name,
|
||||
$cached->cache_id,
|
||||
$cached->compile_id,
|
||||
$cached->content,
|
||||
$timestamp
|
||||
);
|
||||
$cached->timestamp = isset($timestamp) ? $timestamp : false;
|
||||
$cached->exists = !!$cached->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the cached template and process the header
|
||||
*
|
||||
* @param Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
?\Smarty\Template\Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
$cached = $_smarty_tpl->getCached();
|
||||
}
|
||||
$content = $cached->content ? $cached->content : null;
|
||||
$timestamp = $cached->timestamp ? $cached->timestamp : null;
|
||||
if ($content === null || !$timestamp) {
|
||||
$this->fetch(
|
||||
$_smarty_tpl->getCached()->filepath,
|
||||
$_smarty_tpl->getSource()->name,
|
||||
$_smarty_tpl->cache_id,
|
||||
$_smarty_tpl->compile_id,
|
||||
$content,
|
||||
$timestamp
|
||||
);
|
||||
}
|
||||
if (isset($content)) {
|
||||
eval('?>' . $content);
|
||||
$cached->content = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the rendered template output to cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
* @param string $content content to cache
|
||||
*
|
||||
* @return boolean success
|
||||
*/
|
||||
public function storeCachedContent(Template $_template, $content)
|
||||
{
|
||||
return $this->save(
|
||||
$_template->getCached()->filepath,
|
||||
$_template->getSource()->name,
|
||||
$_template->cache_id,
|
||||
$_template->compile_id,
|
||||
$_template->cache_lifetime,
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read cached template from cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return string|boolean content
|
||||
*/
|
||||
public function retrieveCachedContent(Template $_template)
|
||||
{
|
||||
$content = $_template->getCached()->content ?: null;
|
||||
if ($content === null) {
|
||||
$timestamp = null;
|
||||
$this->fetch(
|
||||
$_template->getCached()->filepath,
|
||||
$_template->getSource()->name,
|
||||
$_template->cache_id,
|
||||
$_template->compile_id,
|
||||
$content,
|
||||
$timestamp
|
||||
);
|
||||
}
|
||||
if (isset($content)) {
|
||||
return $content;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache
|
||||
*
|
||||
* @param \Smarty\Smarty $smarty Smarty object
|
||||
* @param null $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return integer number of cache files deleted
|
||||
*/
|
||||
public function clearAll(\Smarty\Smarty $smarty, $exp_time = null)
|
||||
{
|
||||
return $this->delete(null, null, null, $exp_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache for a specific template
|
||||
*
|
||||
* @param \Smarty\Smarty $smarty Smarty object
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param integer $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return int number of cache files deleted
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
public function clear(\Smarty\Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
|
||||
{
|
||||
$cache_name = null;
|
||||
if (isset($resource_name)) {
|
||||
$source = \Smarty\Template\Source::load(null, $smarty, $resource_name);
|
||||
if ($source->exists) {
|
||||
$cache_name = $source->name;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return $this->delete($cache_name, $cache_id, $compile_id, $exp_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is cache is locked for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return boolean true or false if cache is locked
|
||||
*/
|
||||
public function hasLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
|
||||
{
|
||||
$id = $cached->lock_id;
|
||||
$name = $cached->getSource()->name . '.lock';
|
||||
$mtime = $this->fetchTimestamp($id, $name, $cached->cache_id, $cached->compile_id);
|
||||
if ($mtime === null) {
|
||||
$this->fetch($id, $name, $cached->cache_id, $cached->compile_id, $content, $mtime);
|
||||
}
|
||||
return $mtime && ($t = time()) - $mtime < $smarty->locking_timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cache for this template
|
||||
*
|
||||
* @param \Smarty\Smarty $smarty Smarty object
|
||||
* @param \Smarty\Template\Cached $cached cached object
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public function acquireLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
|
||||
{
|
||||
$cached->is_locked = true;
|
||||
$id = $cached->lock_id;
|
||||
$name = $cached->getSource()->name . '.lock';
|
||||
$this->save($id, $name, $cached->cache_id, $cached->compile_id, $smarty->locking_timeout, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cache for this template
|
||||
*
|
||||
* @param \Smarty\Smarty $smarty Smarty object
|
||||
* @param \Smarty\Template\Cached $cached cached object
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public function releaseLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
|
||||
{
|
||||
$cached->is_locked = false;
|
||||
$name = $cached->getSource()->name . '.lock';
|
||||
$this->delete($name, $cached->cache_id, $cached->compile_id, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\Cacheresource;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
use Smarty\Template\Cached;
|
||||
|
||||
/**
|
||||
* Smarty Internal Plugin CacheResource File
|
||||
*
|
||||
|
||||
|
||||
* @author Uwe Tews
|
||||
* @author Rodney Rehm
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class does contain all necessary methods for the HTML cache on file system
|
||||
* Implements the file system as resource for the HTML cache Version using nocache inserts.
|
||||
*/
|
||||
class File extends Base
|
||||
{
|
||||
/**
|
||||
* populate Cached Object with metadata from Resource
|
||||
*
|
||||
* @param Cached $cached cached object
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Cached $cached, Template $_template)
|
||||
{
|
||||
$source = $_template->getSource();
|
||||
$smarty = $_template->getSmarty();
|
||||
$_compile_dir_sep = $smarty->use_sub_dirs ? DIRECTORY_SEPARATOR : '^';
|
||||
$_filepath = $source->uid;
|
||||
$cached->filepath = $smarty->getCacheDir();
|
||||
if (isset($_template->cache_id)) {
|
||||
$cached->filepath .= preg_replace(
|
||||
array(
|
||||
'![^\w|]+!',
|
||||
'![|]+!'
|
||||
),
|
||||
array(
|
||||
'_',
|
||||
$_compile_dir_sep
|
||||
),
|
||||
$_template->cache_id
|
||||
) . $_compile_dir_sep;
|
||||
}
|
||||
if (isset($_template->compile_id)) {
|
||||
$cached->filepath .= preg_replace('![^\w]+!', '_', $_template->compile_id) . $_compile_dir_sep;
|
||||
}
|
||||
// if use_sub_dirs, break file into directories
|
||||
if ($smarty->use_sub_dirs) {
|
||||
$cached->filepath .= $_filepath[ 0 ] . $_filepath[ 1 ] . DIRECTORY_SEPARATOR . $_filepath[ 2 ] .
|
||||
$_filepath[ 3 ] .
|
||||
DIRECTORY_SEPARATOR .
|
||||
$_filepath[ 4 ] . $_filepath[ 5 ] . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
$cached->filepath .= $_filepath . '_' . $source->getBasename();
|
||||
|
||||
if ($smarty->cache_locking) {
|
||||
$cached->lock_id = $cached->filepath . '.lock';
|
||||
}
|
||||
$cached->filepath .= '.php';
|
||||
$cached->timestamp = $cached->exists = is_file($cached->filepath);
|
||||
if ($cached->exists) {
|
||||
$cached->timestamp = filemtime($cached->filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* populate Cached Object with timestamp and exists from Resource
|
||||
*
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populateTimestamp(Cached $cached)
|
||||
{
|
||||
$cached->timestamp = $cached->exists = is_file($cached->filepath);
|
||||
if ($cached->exists) {
|
||||
$cached->timestamp = filemtime($cached->filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the cached template and process its header
|
||||
*
|
||||
* @param Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Cached|null $cached cached object
|
||||
* @param bool $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
$_smarty_tpl->getCached()->setValid(false);
|
||||
if ($update && defined('HHVM_VERSION')) {
|
||||
eval('?>' . file_get_contents($_smarty_tpl->getCached()->filepath));
|
||||
return true;
|
||||
} else {
|
||||
return @include $_smarty_tpl->getCached()->filepath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the rendered template output to cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
* @param string $content content to cache
|
||||
*
|
||||
* @return bool success
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
public function storeCachedContent(Template $_template, $content)
|
||||
{
|
||||
if ($_template->getSmarty()->writeFile($_template->getCached()->filepath, $content) === true) {
|
||||
if (function_exists('opcache_invalidate')
|
||||
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1
|
||||
) {
|
||||
opcache_invalidate($_template->getCached()->filepath, true);
|
||||
} elseif (function_exists('apc_compile_file')) {
|
||||
apc_compile_file($_template->getCached()->filepath);
|
||||
}
|
||||
$cached = $_template->getCached();
|
||||
$cached->timestamp = $cached->exists = is_file($cached->filepath);
|
||||
if ($cached->exists) {
|
||||
$cached->timestamp = filemtime($cached->filepath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read cached template from cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return string content
|
||||
*/
|
||||
public function retrieveCachedContent(Template $_template)
|
||||
{
|
||||
if (is_file($_template->getCached()->filepath)) {
|
||||
return file_get_contents($_template->getCached()->filepath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache
|
||||
*
|
||||
* @param Smarty $smarty
|
||||
* @param integer $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return integer number of cache files deleted
|
||||
*/
|
||||
public function clearAll(Smarty $smarty, $exp_time = null)
|
||||
{
|
||||
return $this->clear($smarty, null, null, null, $exp_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache for a specific template
|
||||
*
|
||||
* @param Smarty $smarty
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param integer $exp_time expiration time (number of seconds, not timestamp)
|
||||
*
|
||||
* @return integer number of cache files deleted
|
||||
*/
|
||||
public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
|
||||
{
|
||||
$_cache_id = isset($cache_id) ? preg_replace('![^\w\|]+!', '_', $cache_id) : null;
|
||||
$_compile_id = isset($compile_id) ? preg_replace('![^\w]+!', '_', $compile_id) : null;
|
||||
$_dir_sep = $smarty->use_sub_dirs ? '/' : '^';
|
||||
$_compile_id_offset = $smarty->use_sub_dirs ? 3 : 0;
|
||||
$_dir = $smarty->getCacheDir();
|
||||
if ($_dir === '/') { //We should never want to delete this!
|
||||
return 0;
|
||||
}
|
||||
$_dir_length = strlen($_dir);
|
||||
if (isset($_cache_id)) {
|
||||
$_cache_id_parts = explode('|', $_cache_id);
|
||||
$_cache_id_parts_count = count($_cache_id_parts);
|
||||
if ($smarty->use_sub_dirs) {
|
||||
foreach ($_cache_id_parts as $id_part) {
|
||||
$_dir .= $id_part . '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($resource_name)) {
|
||||
$_save_stat = $smarty->caching;
|
||||
$smarty->caching = \Smarty\Smarty::CACHING_LIFETIME_CURRENT;
|
||||
$tpl = $smarty->doCreateTemplate($resource_name);
|
||||
$smarty->caching = $_save_stat;
|
||||
// remove from template cache
|
||||
if ($tpl->getSource()->exists) {
|
||||
$_resourcename_parts = basename(str_replace('^', '/', $tpl->getCached()->filepath));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$_count = 0;
|
||||
$_time = time();
|
||||
if (file_exists($_dir)) {
|
||||
$_cacheDirs = new RecursiveDirectoryIterator($_dir);
|
||||
$_cache = new RecursiveIteratorIterator($_cacheDirs, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
foreach ($_cache as $_file) {
|
||||
if (substr(basename($_file->getPathname()), 0, 1) === '.') {
|
||||
continue;
|
||||
}
|
||||
$_filepath = (string)$_file;
|
||||
// directory ?
|
||||
if ($_file->isDir()) {
|
||||
if (!$_cache->isDot()) {
|
||||
// delete folder if empty
|
||||
@rmdir($_file->getPathname());
|
||||
}
|
||||
} else {
|
||||
// delete only php files
|
||||
if (substr($_filepath, -4) !== '.php') {
|
||||
continue;
|
||||
}
|
||||
$_parts = explode($_dir_sep, str_replace('\\', '/', substr($_filepath, $_dir_length)));
|
||||
$_parts_count = count($_parts);
|
||||
// check name
|
||||
if (isset($resource_name)) {
|
||||
if ($_parts[ $_parts_count - 1 ] !== $_resourcename_parts) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// check compile id
|
||||
if (isset($_compile_id) && (!isset($_parts[ $_parts_count - 2 - $_compile_id_offset ])
|
||||
|| $_parts[ $_parts_count - 2 - $_compile_id_offset ] !== $_compile_id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// check cache id
|
||||
if (isset($_cache_id)) {
|
||||
// count of cache id parts
|
||||
$_parts_count = (isset($_compile_id)) ? $_parts_count - 2 - $_compile_id_offset :
|
||||
$_parts_count - 1 - $_compile_id_offset;
|
||||
if ($_parts_count < $_cache_id_parts_count) {
|
||||
continue;
|
||||
}
|
||||
for ($i = 0; $i < $_cache_id_parts_count; $i++) {
|
||||
if ($_parts[ $i ] !== $_cache_id_parts[ $i ]) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_file($_filepath)) {
|
||||
// expired ?
|
||||
if (isset($exp_time)) {
|
||||
if ($exp_time < 0) {
|
||||
preg_match('#\'cache_lifetime\' =>\s*(\d*)#', file_get_contents($_filepath), $match);
|
||||
if ($_time < (filemtime($_filepath) + $match[ 1 ])) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if ($_time - filemtime($_filepath) < $exp_time) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$_count += @unlink($_filepath) ? 1 : 0;
|
||||
if (function_exists('opcache_invalidate')
|
||||
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
|
||||
) {
|
||||
opcache_invalidate($_filepath, true);
|
||||
} elseif (function_exists('apc_delete_file')) {
|
||||
apc_delete_file($_filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is cache is locked for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return boolean true or false if cache is locked
|
||||
*/
|
||||
public function hasLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
clearstatcache(true, $cached->lock_id ?? '');
|
||||
if (null !== $cached->lock_id && is_file($cached->lock_id)) {
|
||||
$t = filemtime($cached->lock_id);
|
||||
return $t && (time() - $t < $smarty->locking_timeout);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function acquireLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
$cached->is_locked = true;
|
||||
touch($cached->lock_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
$cached->is_locked = false;
|
||||
@unlink($cached->lock_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\Cacheresource;
|
||||
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
use Smarty\Template\Cached;
|
||||
|
||||
/**
|
||||
* Smarty Internal Plugin
|
||||
*
|
||||
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smarty Cache Handler Base for Key/Value Storage Implementations
|
||||
* This class implements the functionality required to use simple key/value stores
|
||||
* for hierarchical cache groups. key/value stores like memcache or APC do not support
|
||||
* wildcards in keys, therefore a cache group cannot be cleared like "a|*" - which
|
||||
* is no problem to filesystem and RDBMS implementations.
|
||||
* This implementation is based on the concept of invalidation. While one specific cache
|
||||
* can be identified and cleared, any range of caches cannot be identified. For this reason
|
||||
* each level of the cache group hierarchy can have its own value in the store. These values
|
||||
* are nothing but microtimes, telling us when a particular cache group was cleared for the
|
||||
* last time. These keys are evaluated for every cache read to determine if the cache has
|
||||
* been invalidated since it was created and should hence be treated as inexistent.
|
||||
* Although deep hierarchies are possible, they are not recommended. Try to keep your
|
||||
* cache groups as shallow as possible. Anything up 3-5 parents should be ok. So
|
||||
* »a|b|c« is a good depth where »a|b|c|d|e|f|g|h|i|j|k« isn't. Try to join correlating
|
||||
* cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever«
|
||||
* consider using »a|b|c|$page-$items-$whatever« instead.
|
||||
*
|
||||
|
||||
|
||||
* @author Rodney Rehm
|
||||
*/
|
||||
abstract class KeyValueStore extends Base
|
||||
{
|
||||
/**
|
||||
* cache for contents
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contents = array();
|
||||
|
||||
/**
|
||||
* cache for timestamps
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $timestamps = array();
|
||||
|
||||
/**
|
||||
* populate Cached Object with meta data from Resource
|
||||
*
|
||||
* @param Cached $cached cached object
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Cached $cached, Template $_template)
|
||||
{
|
||||
$cached->filepath = $_template->getSource()->uid . '#' . $this->sanitize($cached->getSource()->resource) . '#' .
|
||||
$this->sanitize($cached->cache_id) . '#' . $this->sanitize($cached->compile_id);
|
||||
$this->populateTimestamp($cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* populate Cached Object with timestamp and exists from Resource
|
||||
*
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populateTimestamp(Cached $cached)
|
||||
{
|
||||
if (!$this->fetch(
|
||||
$cached->filepath,
|
||||
$cached->getSource()->name,
|
||||
$cached->cache_id,
|
||||
$cached->compile_id,
|
||||
$content,
|
||||
$timestamp,
|
||||
$cached->getSource()->uid
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$cached->content = $content;
|
||||
$cached->timestamp = (int)$timestamp;
|
||||
$cached->exists = !!$cached->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the cached template and process the header
|
||||
*
|
||||
* @param Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
$cached = $_smarty_tpl->getCached();
|
||||
}
|
||||
$content = $cached->content ?: null;
|
||||
$timestamp = $cached->timestamp ?: null;
|
||||
if ($content === null || !$timestamp) {
|
||||
if (!$this->fetch(
|
||||
$_smarty_tpl->getCached()->filepath,
|
||||
$_smarty_tpl->getSource()->name,
|
||||
$_smarty_tpl->cache_id,
|
||||
$_smarty_tpl->compile_id,
|
||||
$content,
|
||||
$timestamp,
|
||||
$_smarty_tpl->getSource()->uid
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isset($content)) {
|
||||
eval('?>' . $content);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the rendered template output to cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
* @param string $content content to cache
|
||||
*
|
||||
* @return boolean success
|
||||
*/
|
||||
public function storeCachedContent(Template $_template, $content)
|
||||
{
|
||||
$this->addMetaTimestamp($content);
|
||||
return $this->write(array($_template->getCached()->filepath => $content), $_template->cache_lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read cached template from cache
|
||||
*
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return string|false content
|
||||
*/
|
||||
public function retrieveCachedContent(Template $_template)
|
||||
{
|
||||
$content = $_template->getCached()->content ?: null;
|
||||
$timestamp = null;
|
||||
if ($content === null) {
|
||||
if (!$this->fetch(
|
||||
$_template->getCached()->filepath,
|
||||
$_template->getSource()->name,
|
||||
$_template->cache_id,
|
||||
$_template->compile_id,
|
||||
$content,
|
||||
$timestamp,
|
||||
$_template->getSource()->uid
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isset($content)) {
|
||||
return $content;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache
|
||||
* {@internal the $exp_time argument is ignored altogether }}
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param integer $exp_time expiration time [being ignored]
|
||||
*
|
||||
* @return integer number of cache files deleted [always -1]
|
||||
* @uses purge() to clear the whole store
|
||||
* @uses invalidate() to mark everything outdated if purge() is inapplicable
|
||||
*/
|
||||
public function clearAll(Smarty $smarty, $exp_time = null)
|
||||
{
|
||||
if (!$this->purge()) {
|
||||
$this->invalidate(null);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cache for a specific template
|
||||
* {@internal the $exp_time argument is ignored altogether}}
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param integer $exp_time expiration time [being ignored]
|
||||
*
|
||||
* @return int number of cache files deleted [always -1]
|
||||
* @throws \Smarty\Exception
|
||||
* @uses buildCachedFilepath() to generate the CacheID
|
||||
* @uses invalidate() to mark CacheIDs parent chain as outdated
|
||||
* @uses delete() to remove CacheID from cache
|
||||
*/
|
||||
public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
|
||||
{
|
||||
$uid = $this->getTemplateUid($smarty, $resource_name);
|
||||
$cid = $uid . '#' . $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' .
|
||||
$this->sanitize($compile_id);
|
||||
$this->delete(array($cid));
|
||||
$this->invalidate($cid, $resource_name, $cache_id, $compile_id, $uid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template's unique ID
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param string $resource_name template name
|
||||
*
|
||||
* @return string filepath of cache file
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
protected function getTemplateUid(Smarty $smarty, $resource_name)
|
||||
{
|
||||
if (isset($resource_name)) {
|
||||
$source = \Smarty\Template\Source::load(null, $smarty, $resource_name);
|
||||
if ($source->exists) {
|
||||
return $source->uid;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize CacheID components
|
||||
*
|
||||
* @param string $string CacheID component to sanitize
|
||||
*
|
||||
* @return string sanitized CacheID component
|
||||
*/
|
||||
protected function sanitize($string)
|
||||
{
|
||||
$string = trim((string)$string, '|');
|
||||
if (!$string) {
|
||||
return '';
|
||||
}
|
||||
return preg_replace('#[^\w\|]+#S', '_', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and prepare a cache object.
|
||||
*
|
||||
* @param string $cid CacheID to fetch
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param string $content cached content
|
||||
* @param integer &$timestamp cached timestamp (epoch)
|
||||
* @param string $resource_uid resource's uid
|
||||
*
|
||||
* @return boolean success
|
||||
*/
|
||||
protected function fetch(
|
||||
$cid,
|
||||
$resource_name = null,
|
||||
$cache_id = null,
|
||||
$compile_id = null,
|
||||
&$content = null,
|
||||
&$timestamp = null,
|
||||
$resource_uid = null
|
||||
) {
|
||||
$t = $this->read(array($cid));
|
||||
$content = !empty($t[ $cid ]) ? $t[ $cid ] : null;
|
||||
$timestamp = null;
|
||||
if ($content && ($timestamp = $this->getMetaTimestamp($content))) {
|
||||
$invalidated =
|
||||
$this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id, $resource_uid);
|
||||
if ($invalidated > $timestamp) {
|
||||
$timestamp = null;
|
||||
$content = null;
|
||||
}
|
||||
}
|
||||
return !!$content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add current microtime to the beginning of $cache_content
|
||||
* {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}}
|
||||
*
|
||||
* @param string &$content the content to be cached
|
||||
*/
|
||||
protected function addMetaTimestamp(&$content)
|
||||
{
|
||||
$mt = explode(' ', microtime());
|
||||
$ts = pack('NN', $mt[ 1 ], (int)($mt[ 0 ] * 100000000));
|
||||
$content = $ts . $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the timestamp the $content was cached
|
||||
*
|
||||
* @param string &$content the cached content
|
||||
*
|
||||
* @return float the microtime the content was cached
|
||||
*/
|
||||
protected function getMetaTimestamp(&$content)
|
||||
{
|
||||
extract(unpack('N1s/N1m/a*content', $content));
|
||||
/**
|
||||
* @var int $s
|
||||
* @var int $m
|
||||
*/
|
||||
return $s + ($m / 100000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate CacheID
|
||||
*
|
||||
* @param string $cid CacheID
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param string $resource_uid source's uid
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function invalidate(
|
||||
$cid = null,
|
||||
$resource_name = null,
|
||||
$cache_id = null,
|
||||
$compile_id = null,
|
||||
$resource_uid = null
|
||||
) {
|
||||
$now = microtime(true);
|
||||
$key = null;
|
||||
// invalidate everything
|
||||
if (!$resource_name && !$cache_id && !$compile_id) {
|
||||
$key = 'IVK#ALL';
|
||||
} // invalidate all caches by template
|
||||
else {
|
||||
if ($resource_name && !$cache_id && !$compile_id) {
|
||||
$key = 'IVK#TEMPLATE#' . $resource_uid . '#' . $this->sanitize($resource_name);
|
||||
} // invalidate all caches by cache group
|
||||
else {
|
||||
if (!$resource_name && $cache_id && !$compile_id) {
|
||||
$key = 'IVK#CACHE#' . $this->sanitize($cache_id);
|
||||
} // invalidate all caches by compile id
|
||||
else {
|
||||
if (!$resource_name && !$cache_id && $compile_id) {
|
||||
$key = 'IVK#COMPILE#' . $this->sanitize($compile_id);
|
||||
} // invalidate by combination
|
||||
else {
|
||||
$key = 'IVK#CID#' . $cid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->write(array($key => $now));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the latest timestamp known to the invalidation chain
|
||||
*
|
||||
* @param string $cid CacheID to determine latest invalidation timestamp of
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param string $resource_uid source's filepath
|
||||
*
|
||||
* @return float the microtime the CacheID was invalidated
|
||||
*/
|
||||
protected function getLatestInvalidationTimestamp(
|
||||
$cid,
|
||||
$resource_name = null,
|
||||
$cache_id = null,
|
||||
$compile_id = null,
|
||||
$resource_uid = null
|
||||
) {
|
||||
// abort if there are no InvalidationKeys to check
|
||||
if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id, $resource_uid))) {
|
||||
return 0;
|
||||
}
|
||||
// there are no InValidationKeys
|
||||
if (!($values = $this->read($_cid))) {
|
||||
return 0;
|
||||
}
|
||||
// make sure we're dealing with floats
|
||||
$values = array_map('floatval', $values);
|
||||
return max($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a CacheID into the list of applicable InvalidationKeys.
|
||||
* Splits 'some|chain|into|an|array' into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... )
|
||||
*
|
||||
* @param string $cid CacheID to translate
|
||||
* @param string $resource_name template name
|
||||
* @param string $cache_id cache id
|
||||
* @param string $compile_id compile id
|
||||
* @param string $resource_uid source's filepath
|
||||
*
|
||||
* @return array list of InvalidationKeys
|
||||
* @uses $invalidationKeyPrefix to prepend to each InvalidationKey
|
||||
*/
|
||||
protected function listInvalidationKeys(
|
||||
$cid,
|
||||
$resource_name = null,
|
||||
$cache_id = null,
|
||||
$compile_id = null,
|
||||
$resource_uid = null
|
||||
) {
|
||||
$t = array('IVK#ALL');
|
||||
$_name = $_compile = '#';
|
||||
if ($resource_name) {
|
||||
$_name .= $resource_uid . '#' . $this->sanitize($resource_name);
|
||||
$t[] = 'IVK#TEMPLATE' . $_name;
|
||||
}
|
||||
if ($compile_id) {
|
||||
$_compile .= $this->sanitize($compile_id);
|
||||
$t[] = 'IVK#COMPILE' . $_compile;
|
||||
}
|
||||
$_name .= '#';
|
||||
$cid = trim((string)$cache_id, '|');
|
||||
if (!$cid) {
|
||||
return $t;
|
||||
}
|
||||
$i = 0;
|
||||
while (true) {
|
||||
// determine next delimiter position
|
||||
$i = strpos($cid, '|', $i);
|
||||
// add complete CacheID if there are no more delimiters
|
||||
if ($i === false) {
|
||||
$t[] = 'IVK#CACHE#' . $cid;
|
||||
$t[] = 'IVK#CID' . $_name . $cid . $_compile;
|
||||
$t[] = 'IVK#CID' . $_name . $_compile;
|
||||
break;
|
||||
}
|
||||
$part = substr($cid, 0, $i);
|
||||
// add slice to list
|
||||
$t[] = 'IVK#CACHE#' . $part;
|
||||
$t[] = 'IVK#CID' . $_name . $part . $_compile;
|
||||
// skip past delimiter position
|
||||
$i++;
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is cache is locked for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return boolean true or false if cache is locked
|
||||
*/
|
||||
public function hasLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
$key = 'LOCK#' . $cached->filepath;
|
||||
$data = $this->read(array($key));
|
||||
return $data && time() - $data[ $key ] < $smarty->locking_timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public function acquireLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
$cached->is_locked = true;
|
||||
$key = 'LOCK#' . $cached->filepath;
|
||||
$this->write(array($key => time()), $smarty->locking_timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock cache for this template
|
||||
*
|
||||
* @param Smarty $smarty Smarty object
|
||||
* @param Cached $cached cached object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
$cached->is_locked = false;
|
||||
$key = 'LOCK#' . $cached->filepath;
|
||||
$this->delete(array($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read values for a set of keys from cache
|
||||
*
|
||||
* @param array $keys list of keys to fetch
|
||||
*
|
||||
* @return array list of values with the given keys used as indexes
|
||||
*/
|
||||
abstract protected function read(array $keys);
|
||||
|
||||
/**
|
||||
* Save values for a set of keys to cache
|
||||
*
|
||||
* @param array $keys list of values to save
|
||||
* @param int $expire expiration time
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
abstract protected function write(array $keys, $expire = null);
|
||||
|
||||
/**
|
||||
* Remove values from cache
|
||||
*
|
||||
* @param array $keys list of keys to delete
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
abstract protected function delete(array $keys);
|
||||
|
||||
/**
|
||||
* Remove *all* values from cache
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
protected function purge()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty Internal Compile Plugin Base
|
||||
* @author Uwe Tews
|
||||
*/
|
||||
namespace Smarty\Compile;
|
||||
|
||||
use Smarty\Compiler\Template;
|
||||
use Smarty\Data;
|
||||
use Smarty\Exception;
|
||||
|
||||
/**
|
||||
* This class does extend all internal compile plugins
|
||||
*
|
||||
|
||||
|
||||
*/
|
||||
abstract class Base implements CompilerInterface {
|
||||
|
||||
/**
|
||||
* Array of names of required attribute required by tag
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $required_attributes = [];
|
||||
|
||||
/**
|
||||
* Array of names of optional attribute required by tag
|
||||
* use array('_any') if there is no restriction of attributes names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $optional_attributes = [];
|
||||
|
||||
/**
|
||||
* Shorttag attribute order defined by its names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $shorttag_order = [];
|
||||
|
||||
/**
|
||||
* Array of names of valid option flags
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $option_flags = ['nocache'];
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $cacheable = true;
|
||||
|
||||
public function isCacheable(): bool {
|
||||
return $this->cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts attributes into parameter array strings
|
||||
*
|
||||
* @param array $_attr
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function formatParamsArray(array $_attr): array {
|
||||
$_paramsArray = [];
|
||||
foreach ($_attr as $_key => $_value) {
|
||||
$_paramsArray[] = var_export($_key, true) . "=>" . $_value;
|
||||
}
|
||||
return $_paramsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the attributes passed are valid
|
||||
* The attributes passed for the tag to compile are checked against the list of required and
|
||||
* optional attributes. Required attributes must be present. Optional attributes are check against
|
||||
* the corresponding list. The keyword '_any' specifies that any attribute will be accepted
|
||||
* as valid
|
||||
*
|
||||
* @param object $compiler compiler object
|
||||
* @param array $attributes attributes applied to the tag
|
||||
*
|
||||
* @return array of mapped attributes for further processing
|
||||
*/
|
||||
protected function getAttributes($compiler, $attributes) {
|
||||
$_indexed_attr = [];
|
||||
$options = array_fill_keys($this->option_flags, true);
|
||||
foreach ($attributes as $key => $mixed) {
|
||||
// shorthand ?
|
||||
if (!is_array($mixed)) {
|
||||
// options flag ?
|
||||
if (isset($options[trim($mixed, '\'"')])) {
|
||||
$_indexed_attr[trim($mixed, '\'"')] = true;
|
||||
// shorthand attribute ?
|
||||
} elseif (isset($this->shorttag_order[$key])) {
|
||||
$_indexed_attr[$this->shorttag_order[$key]] = $mixed;
|
||||
} else {
|
||||
// too many shorthands
|
||||
$compiler->trigger_template_error('too many shorthand attributes', null, true);
|
||||
}
|
||||
// named attribute
|
||||
} else {
|
||||
foreach ($mixed as $k => $v) {
|
||||
// options flag?
|
||||
if (isset($options[$k])) {
|
||||
if (is_bool($v)) {
|
||||
$_indexed_attr[$k] = $v;
|
||||
} else {
|
||||
if (is_string($v)) {
|
||||
$v = trim($v, '\'" ');
|
||||
}
|
||||
|
||||
// Mapping array for boolean option value
|
||||
static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];
|
||||
|
||||
if (isset($optionMap[$v])) {
|
||||
$_indexed_attr[$k] = $optionMap[$v];
|
||||
} else {
|
||||
$compiler->trigger_template_error(
|
||||
"illegal value '" . var_export($v, true) .
|
||||
"' for options flag '{$k}'",
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
// must be named attribute
|
||||
} else {
|
||||
$_indexed_attr[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if all required attributes present
|
||||
foreach ($this->required_attributes as $attr) {
|
||||
if (!isset($_indexed_attr[$attr])) {
|
||||
$compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
// check for not allowed attributes
|
||||
if ($this->optional_attributes !== ['_any']) {
|
||||
$allowedAttributes = array_fill_keys(
|
||||
array_merge(
|
||||
$this->required_attributes,
|
||||
$this->optional_attributes,
|
||||
$this->option_flags
|
||||
),
|
||||
true
|
||||
);
|
||||
foreach ($_indexed_attr as $key => $dummy) {
|
||||
if (!isset($allowedAttributes[$key]) && $key !== 0) {
|
||||
$compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// default 'false' for all options flags not set
|
||||
foreach ($this->option_flags as $flag) {
|
||||
if (!isset($_indexed_attr[$flag])) {
|
||||
$_indexed_attr[$flag] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $_indexed_attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push opening tag name on stack
|
||||
* Optionally additional data can be saved on stack
|
||||
*
|
||||
* @param Template $compiler compiler object
|
||||
* @param string $openTag the opening tag's name
|
||||
* @param mixed $data optional data saved
|
||||
*/
|
||||
protected function openTag(Template $compiler, $openTag, $data = null) {
|
||||
$compiler->openTag($openTag, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop closing tag
|
||||
* Raise an error if this stack-top doesn't match with expected opening tags
|
||||
*
|
||||
* @param Template $compiler compiler object
|
||||
* @param array|string $expectedTag the expected opening tag names
|
||||
*
|
||||
* @return mixed any type the opening tag's name or saved data
|
||||
*/
|
||||
protected function closeTag(Template $compiler, $expectedTag) {
|
||||
return $compiler->closeTag($expectedTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $scope
|
||||
* @param array $invalidScopes
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function convertScope($scope): int {
|
||||
|
||||
static $scopes = [
|
||||
'local' => Data::SCOPE_LOCAL, // current scope
|
||||
'parent' => Data::SCOPE_PARENT, // parent scope (definition unclear)
|
||||
'tpl_root' => Data::SCOPE_TPL_ROOT, // highest template (keep going up until parent is not a template)
|
||||
'root' => Data::SCOPE_ROOT, // highest scope (definition unclear)
|
||||
'global' => Data::SCOPE_GLOBAL, // smarty object
|
||||
|
||||
'smarty' => Data::SCOPE_SMARTY, // @deprecated alias of 'global'
|
||||
];
|
||||
|
||||
$_scopeName = trim($scope, '\'"');
|
||||
if (is_numeric($_scopeName) && in_array($_scopeName, $scopes)) {
|
||||
return (int) $_scopeName;
|
||||
}
|
||||
|
||||
if (isset($scopes[$_scopeName])) {
|
||||
return $scopes[$_scopeName];
|
||||
}
|
||||
|
||||
$err = var_export($_scopeName, true);
|
||||
throw new Exception("illegal value '{$err}' for \"scope\" attribute");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles code for the tag
|
||||
*
|
||||
* @param array $args array with attributes from parser
|
||||
* @param Template $compiler compiler object
|
||||
* @param array $parameter array with compilation parameter
|
||||
*
|
||||
* @return string compiled code as a string
|
||||
* @throws \Smarty\CompilerException
|
||||
*/
|
||||
abstract public function compile($args, Template $compiler, $parameter = array(), $tag = null, $function = null): string;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user