Commit 96e794b4 by Paul Klimov

Merge branch 'master' of github.com:yiisoft/yii2 into mongo

parents dfefd060 3db7a075
......@@ -70,7 +70,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
You can then install the application using the following command:
~~~
php composer.phar create-project --stability=alpha yiisoft/yii2-app-advanced advanced
php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced advanced
~~~
......
......@@ -52,7 +52,7 @@ at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
You can then install this application template using the following command:
~~~
php composer.phar create-project --stability=alpha yiisoft/yii2-app-basic basic
php composer.phar create-project --stability=dev yiisoft/yii2-app-basic basic
~~~
Now you should be able to access the application through the following URL, assuming `basic` is the directory
......
......@@ -48,6 +48,13 @@ $requirements = [
'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
'memo' => 'Required for MySQL database.',
],
[
'name' => 'PDO PostgreSQL extension',
'mandatory' => false,
'condition' => extension_loaded('pdo_pgsql'),
'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
'memo' => 'Required for PostgreSQL database.',
],
// Cache :
[
'name' => 'Memcache extension',
......
......@@ -125,7 +125,7 @@ class BlogController extends Controller
throw new NotFoundHttpException;
}
if (\Yii::$app->request->isPost)) {
if (\Yii::$app->request->isPost) {
$post->load($_POST);
if ($post->save()) {
$this->redirect(['view', 'id' => $post->id]);
......
......@@ -2,8 +2,8 @@ URL Management
==============
The concept of URL management in Yii fairly simple. URL management is based on the premise that the application uses internal routes and parameters
everywhere. The framework itself will then translates routes into URLs, and translate URLs into routs, according to the URL manager's configuration.
This approach allows you to change site-wide URLs merely by edited a single config file, without ever touching the application code.
everywhere. The framework itself will then translates routes into URLs, and translate URLs into routes, according to the URL manager's configuration.
This approach allows you to change site-wide URLs merely by editing a single config file, without ever touching the application code.
Internal route
--------------
......
......@@ -61,7 +61,8 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
});
foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) {
$this->panels = array_merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) {
$config['module'] = $this;
$config['id'] = $id;
$this->panels[$id] = Yii::createObject($config);
......
......@@ -31,6 +31,12 @@ class Panel extends Component
*/
public $module;
public $data;
/**
* @var array array of actions to add to the debug modules default controller.
* This array will be merged with all other panels actions property.
* See [[yii\base\Controller::actions()]] for the format.
*/
public $actions = [];
/**
* @return string name of the panel
......
......@@ -27,6 +27,15 @@ class DefaultController extends Controller
*/
public $summary;
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function actionIndex()
{
return $this->render('index', ['manifest' => $this->getManifest()]);
......@@ -82,7 +91,7 @@ class DefaultController extends Controller
return $this->_manifest;
}
protected function loadData($tag)
public function loadData($tag)
{
$manifest = $this->getManifest();
if (isset($manifest[$tag])) {
......
......@@ -94,7 +94,7 @@ class Command extends Component
*/
public function get($index, $type, $id, $options = [])
{
return $this->db->get([$index, $type, $id], $options, null, [200, 404]);
return $this->db->get([$index, $type, $id], $options, null);
}
/**
......
......@@ -177,10 +177,10 @@ class Connection extends Component
return new QueryBuilder($this);
}
public function get($url, $options = [], $body = null)
public function get($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('GET', $this->createUrl($url, $options), $body);
return $this->httpRequest('GET', $this->createUrl($url, $options), $body, $raw);
}
public function head($url, $options = [], $body = null)
......@@ -189,37 +189,43 @@ class Connection extends Component
return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body);
}
public function post($url, $options = [], $body = null)
public function post($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('POST', $this->createUrl($url, $options), $body);
return $this->httpRequest('POST', $this->createUrl($url, $options), $body, $raw);
}
public function put($url, $options = [], $body = null)
public function put($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body);
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body, $raw);
}
public function delete($url, $options = [], $body = null)
public function delete($url, $options = [], $body = null, $raw = false)
{
$this->open();
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body);
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body, $raw);
}
private function createUrl($path, $options = [])
{
if (!is_string($path)) {
$url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($options)) {
$url .= '?' . http_build_query($options);
}
} else {
$url = $path;
if (!empty($options)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options);
}
}
return [$this->nodes[$this->activeNode]['http_address'], $url];
}
protected function httpRequest($method, $url, $requestBody = null)
protected function httpRequest($method, $url, $requestBody = null, $raw = false)
{
$method = strtoupper($method);
......@@ -228,7 +234,7 @@ class Connection extends Component
$body = '';
$options = [
CURLOPT_USERAGENT => 'Yii2 Framework ' . __CLASS__,
CURLOPT_USERAGENT => 'Yii Framework 2 ' . __CLASS__,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
// http://www.php.net/manual/en/function.curl-setopt.php#82418
......@@ -264,8 +270,11 @@ class Connection extends Component
if (is_array($url)) {
list($host, $q) = $url;
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
if (strncmp($host, 'inet[', 5) == 0) {
$host = substr($host, 5, -1);
if (($pos = strpos($host, '/')) !== false) {
$host = substr($host, $pos + 1);
}
}
$profile = $method . ' ' . $q . '#' . $requestBody;
$url = 'http://' . $host . '/' . $q;
......@@ -312,7 +321,7 @@ class Connection extends Component
]);
}
if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) {
return Json::decode($body);
return $raw ? $body : Json::decode($body);
}
throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [
'requestMethod' => $method,
......
<?php
/**
* @author Carsten Brandt <mail@cebe.cc>
*/
namespace yii\elasticsearch;
use yii\base\Action;
use yii\base\NotSupportedException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use Yii;
use yii\web\Response;
class DebugAction extends Action
{
/**
* @var string the connection id to use
*/
public $db;
/**
* @var Panel
*/
public $panel;
public function run($logId, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
if (!isset($timings[$logId])) {
throw new HttpException(404, 'Log message not found.');
}
$message = $timings[$logId][1];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$method = mb_substr($url, 0, $pos = mb_strpos($url, ' '));
$url = mb_substr($url, $pos + 1);
$options = ['pretty' => true];
/** @var Connection $db */
$db = \Yii::$app->getComponent($this->db);
$time = microtime(true);
switch($method) {
case 'GET': $result = $db->get($url, $options, $body, true); break;
case 'POST': $result = $db->post($url, $options, $body, true); break;
case 'PUT': $result = $db->put($url, $options, $body, true); break;
case 'DELETE': $result = $db->delete($url, $options, $body, true); break;
case 'HEAD': $result = $db->head($url, $options, $body); break;
default:
throw new NotSupportedException("Request method '$method' is not supported by elasticsearch.");
}
$time = microtime(true) - $time;
if ($result === true) {
$result = '<span class="label label-success">success</span>';
} elseif ($result === false) {
$result = '<span class="label label-danger">no success</span>';
}
Yii::$app->response->format = Response::FORMAT_JSON;
return [
'time' => sprintf('%.1f ms', $time * 1000),
'result' => $result,
];
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@
namespace yii\elasticsearch;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger;
use yii\helpers\Html;
use yii\web\View;
......@@ -20,6 +21,17 @@ use yii\web\View;
*/
class DebugPanel extends Panel
{
public $db = 'elasticsearch';
public function init()
{
$this->actions['elasticsearch-query'] = [
'class' => 'yii\\elasticsearch\\DebugAction',
'panel' => $this,
'db' => $this->db,
];
}
public function getName()
{
return 'Elasticsearch';
......@@ -47,13 +59,14 @@ EOD;
public function getDetail()
{
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = [];
$i = 0;
foreach ($this->data['messages'] as $log) {
list ($message, $level, $category, $time, $traces) = $log;
if ($level == Logger::LEVEL_PROFILE_BEGIN) {
continue;
}
foreach ($timings as $logId => $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);
$message = $timing[1];
$traces = $timing[4];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
......@@ -70,44 +83,39 @@ EOD;
},
]);
}
$runLinks = '';
$c = 0;
\Yii::$app->elasticsearch->open();
foreach(\Yii::$app->elasticsearch->nodes as $node) {
$pos = mb_strpos($url, ' ');
$type = mb_substr($url, 0, $pos);
if ($type == 'GET' && !empty($body)) {
$type = 'POST';
}
$host = $node['http_address'];
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
}
$nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1);
$nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true';
$nodeBody = json_encode($body);
$ajaxUrl = Html::url(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i-$c').on('click', function() {
$('#elastic-result-$i').html('Sending $type request to $nodeUrl...');
$('#elastic-result-$i').parent('tr').show();
$('#elastic-link-$i').on('click', function() {
var result = $('#elastic-result-$i');
result.html('Sending request...');
result.parent('tr').show();
$.ajax({
type: "$type",
url: "$nodeUrl",
body: $nodeBody,
type: "POST",
url: "$ajaxUrl",
success: function( data ) {
$('#elastic-result-$i').html(data);
$('#elastic-time-$i').html(data.time);
$('#elastic-result-$i').html(data.result);
},
dataType: "text"
error: function(jqXHR, textStatus, errorThrown) {
$('#elastic-time-$i').html('');
$('#elastic-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
},
dataType: "json"
});
return false;
});
JS
, View::POS_READY);
$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>';
$c++;
}
$rows[] = "<tr><td style=\"width: 80%;\"><div><b>$url</b><br/><p>$body</p>$traceString</div></td><td style=\"width: 20%;\">$runLinks</td></tr><tr style=\"display: none;\"><td colspan=\"2\" id=\"elastic-result-$i\"></td></tr>";
$runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$rows[] = <<<HTML
<tr>
<td style="width: 10%;">$duration</td>
<td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
<td style="width: 15%;">$runLink</td>
</tr>
<tr style="display: none;"><td id="elastic-time-$i"></td><td colspan="3" id="elastic-result-$i"></td></tr>
HTML;
$i++;
}
$rows = implode("\n", $rows);
......@@ -117,8 +125,9 @@ JS
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 80%;">Url / Query</th>
<th style="width: 20%;">Run Query on node</th>
<th style="width: 10%;">Time</th>
<th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr>
</thead>
<tbody>
......@@ -130,7 +139,7 @@ HTML;
private $_timings;
protected function calculateTimings()
public function calculateTimings()
{
if ($this->_timings !== null) {
return $this->_timings;
......
......@@ -172,3 +172,5 @@ Add the following to you application config to enable it:
],
// ...
```
![elasticsearch DebugPanel](README-debug.png)
\ No newline at end of file
......@@ -348,7 +348,6 @@ class Generator extends \yii\gii\Generator
case Schema::TYPE_TIME:
case Schema::TYPE_DATETIME:
case Schema::TYPE_TIMESTAMP:
case
$conditions[] = "\$this->addCondition(\$query, '{$column}');";
break;
default:
......
......@@ -397,10 +397,10 @@ class Generator extends \yii\gii\Generator
}
$name = $rawName = Inflector::id2camel($key, '_');
$i = 0;
while (isset($table->columns[$name])) {
while (isset($table->columns[lcfirst($name)])) {
$name = $rawName . ($i++);
}
while (isset($relations[$className][$name])) {
while (isset($relations[$className][lcfirst($name)])) {
$name = $rawName . ($i++);
}
......
......@@ -128,7 +128,6 @@
data = $form.data('yiiActiveForm');
if (data.validated) {
// continue submitting the form since validation passes
data.validated = false;
return true;
}
......
......@@ -23,10 +23,13 @@ use yii\base\Component;
*
* ~~~
* $query = new Query;
* // compose the query
* $query->select('id, name')
* ->from('tbl_user')
* ->limit(10);
* // build and execute the query
* $rows = $query->all();
* // alternatively, you can create DB command and execute it
* $command = $query->createCommand();
* // $command->sql returns the actual SQL
* $rows = $command->queryAll();
......
......@@ -327,6 +327,29 @@ class BaseArrayHelper
}
/**
* Checks if the given array contains the specified key.
* This method enhances the `array_key_exists()` function by supporting case-insensitive
* key comparison.
* @param string $key the key to check
* @param array $array the array with keys to check
* @param boolean $caseSensitive whether the key comparison should be case-sensitive
* @return boolean whether the array contains the specified key
*/
public static function keyExists($key, $array, $caseSensitive = true)
{
if ($caseSensitive) {
return array_key_exists($key, $array);
} else {
foreach (array_keys($array) as $k) {
if (strcasecmp($key, $k) === 0) {
return true;
}
}
return false;
}
}
/**
* Sorts an array of objects or arrays (with the same structure) by one or several keys.
* @param array $array the array to be sorted. The array will be modified after calling this method.
* @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
......
......@@ -551,8 +551,11 @@ class BaseHtml
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the radio button will be enclosed by a label tag.
* - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* The rest of the options will be rendered as the attributes of the resulting radio button tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
*
* @return string the generated radio button tag
......@@ -571,9 +574,14 @@ class BaseHtml
if (isset($options['label'])) {
$label = $options['label'];
$labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : [];
unset($options['label'], $options['labelOptions']);
$container = isset($options['container']) ? $options['container'] : ['class' => 'radio'];
unset($options['label'], $options['labelOptions'], $options['container']);
$content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions);
return $hidden . static::tag('div', $content, ['class' => 'radio']);
if (is_array($container)) {
return $hidden . static::tag('div', $content, $container);
} else {
return $hidden . $content;
}
} else {
return $hidden . static::input('radio', $name, $value, $options);
}
......@@ -592,8 +600,11 @@ class BaseHtml
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the checkbox will be enclosed by a label tag.
* - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* The rest of the options will be rendered as the attributes of the resulting checkbox tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
*
* @return string the generated checkbox tag
......@@ -612,9 +623,14 @@ class BaseHtml
if (isset($options['label'])) {
$label = $options['label'];
$labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : [];
unset($options['label'], $options['labelOptions']);
$container = isset($options['container']) ? $options['container'] : ['class' => 'checkbox'];
unset($options['label'], $options['labelOptions'], $options['container']);
$content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions);
return $hidden . static::tag('div', $content, ['class' => 'checkbox']);
if (is_array($container)) {
return $hidden . static::tag('div', $content, $container);
} else {
return $hidden . $content;
}
} else {
return $hidden . static::input('checkbox', $name, $value, $options);
}
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
/**
* Test Sphinx ActiveRecord class
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
use yii\sphinx\ActiveRelation;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
use yii\db\ActiveRelation;
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
class ItemIndex extends ActiveRecord
{
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
class RuntimeIndex extends ActiveRecord
{
......
<?php
namespace yiiunit\data\sphinx\ar;
namespace yiiunit\data\ar\sphinx;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
......
......@@ -4,8 +4,8 @@ namespace yiiunit\extensions\sphinx;
use yii\data\ActiveDataProvider;
use yii\sphinx\Query;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\sphinx\ArticleIndex;
/**
* @group sphinx
......
......@@ -3,9 +3,9 @@
namespace yiiunit\extensions\sphinx;
use yii\sphinx\ActiveQuery;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\RuntimeIndex;
use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\ar\sphinx\RuntimeIndex;
/**
* @group sphinx
......@@ -41,10 +41,9 @@ class ActiveRecordTest extends SphinxTestCase
$this->assertTrue($articles[1] instanceof ArticleIndex);
// find fulltext
$articles = ArticleIndex::find('cats');
$this->assertEquals(1, count($articles));
$this->assertTrue($articles[0] instanceof ArticleIndex);
$this->assertEquals(1, $articles[0]->id);
$article = ArticleIndex::find(2);
$this->assertTrue($article instanceof ArticleIndex);
$this->assertEquals(2, $article->id);
// find by column values
$article = ArticleIndex::find(['id' => 2, 'author_id' => 2]);
......@@ -168,7 +167,7 @@ class ActiveRecordTest extends SphinxTestCase
$record = RuntimeIndex::find(['id' => 2]);
$record->content = 'Test content with ' . $query;
$record->save();
$rows = RuntimeIndex::find($query);
$rows = RuntimeIndex::find()->match($query);
$this->assertNotEmpty($rows);
// updateAll
......
......@@ -2,10 +2,10 @@
namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb;
use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\ar\sphinx\ArticleDb;
/**
* @group sphinx
......
......@@ -2,11 +2,11 @@
namespace yiiunit\extensions\sphinx;
use yiiunit\data\sphinx\ar\ActiveRecord;
use yiiunit\data\ar\sphinx\ActiveRecord;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
use yiiunit\data\sphinx\ar\ArticleIndex;
use yiiunit\data\sphinx\ar\ArticleDb;
use yiiunit\data\sphinx\ar\TagDb;
use yiiunit\data\ar\sphinx\ArticleIndex;
use yiiunit\data\ar\sphinx\ArticleDb;
use yiiunit\data\ar\sphinx\TagDb;
/**
* @group sphinx
......
......@@ -303,4 +303,21 @@ class ArrayHelperTest extends TestCase
],
], $result);
}
public function testKeyExists()
{
$array = [
'a' => 1,
'B' => 2,
];
$this->assertTrue(ArrayHelper::keyExists('a', $array));
$this->assertFalse(ArrayHelper::keyExists('b', $array));
$this->assertTrue(ArrayHelper::keyExists('B', $array));
$this->assertFalse(ArrayHelper::keyExists('c', $array));
$this->assertTrue(ArrayHelper::keyExists('a', $array, false));
$this->assertTrue(ArrayHelper::keyExists('b', $array, false));
$this->assertTrue(ArrayHelper::keyExists('B', $array, false));
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment