I am working on a custom extension/special page for the first time. I am trying to create a simple page that queries the database and display the result on the page. I got the following code that does that:
class SpecialBuildRating extends SpecialPage {
function __construct() {
parent::__construct( 'BuildRating' );
}
function execute( $par ) {
if(isset($_GET['id'])){
$buildId = $_GET['id'];
$db = wfGetDB( DB_SLAVE );
$res = $db->select(
'build_rating',
array('article_id', 'user_id', 'vote', 'comment', 'date'),
'article_id = 1485', //BuildId instead of 1485
__METHOD__,
array( 'ORDER BY' => 'date ASC' )
);
}
$request = $this->getRequest();
$output = $this->getOutput();
$this->setHeaders();
# Get request data from, e.g.
$param = $request->getText( 'param' );
# Do stuff
# ...
$wikitext = 'Hello world!';
$output->addWikiText( $wikitext );
$outP = '<table style="width:100%">
<tr>
<td>article_id</td>
<td>user_id</td>
<td>vote</td>
<td>comment</td>
<td>date</td>
</tr>
';
if ($res != null) {
foreach( $res as $row ) {
$outP .= '<td>' . $row->article_id . '</td><td>' . $row->user_id . '</td><td>' . $row->vote . '</td><td>' . $row->comment . '</td><td>' . $row->date . '</td>';
}
}
$output->addWikiText( $outP );
}
}
How do I pass the $buildIdto the WHERE statement instead of 1485 in a safe way that prevents injection?
Another question that I have that isn't really an issue is the $output->addWikiText($var); output call, is there any easier/more effective way to do it?
$res = $db->select(
'build_rating',
array('article_id', 'user_id', 'vote', 'comment', 'date'),
array( 'article_id' => $buildId ),
__METHOD__,
array( 'ORDER BY' => 'date ASC' )
);
See https://www.mediawiki.org/wiki/Manual:Database_access for details.
As of outputting, use $output->addHTML(), however in that case you're responsible yourself for preventing XSS.
Another point, in MediaWiki it's recommended to use $this->getRequest()->getInt( 'name', $defaultValue ) instead of accessing request globals directly.
Related
So I have a datatable which shows a paginated list of invoices. Each row has the invoice number, the client it belongs to and some other details about the invoice. My issue is if i try to sort the table by client it breaks the query and throws a mysql error.
I realise this is due to the fact that the clients title is not in the invoice, it is using the clients id. How can i do this? I'm using datatables.net method. In my layout i have the following in the head linking to this library:
<script type="text/javascript" src="https://cdn.datatables.net/v/dt/jszip-2.5.0/dt-1.12.1/b-2.2.3/b-colvis-2.2.3/b-html5-2.2.3/cr-1.5.6/r-2.3.0/datatables.min.js"></script>
This works by the blade template showing only the header and using ajax to call a function which returns the required data contents. The datatables.net js file creates the filters and a search bar.
<table id="invoices" class="eclipse table table-striped table-bordered table-hover">
<thead>
<td style="width:34px;">ID</td>
<td style="width:120px">Invoice #</td>
<td style="width:128px">Ref Number</td>
<td style="width:128px">Client</td>
<td style="width:80px">Date</td>
<td style="width:70px">Hours</td>
<td style="width:100px">Amount</td>
<td style="width:124px">Itemised Work Items</td>
<td>Comment</td>
<td style="width:32px;">Can Edit?</td>
<td style="width:96px">Status</td>
<td style="width:16px;">...</td>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
var loader = document.getElementById("loader");
var menu = document.getElementById("menu-holder");
var span = document.getElementById("close-menu");
var modal = document.getElementById("myModal");
$(document).ready(function() {
var table = $('#invoices').DataTable({
"responsive": true,
"order": [0, 'desc'],
"serverSide": true,
"ajax": {
url: "/invoices/data/",
method: "get"
},
"columnDefs" : [
{
"targets": [-1],
"data": null,
"visible": true,
"defaultContent": '<button>{!! config( 'ecl.BURGER' ) !!}</button>'
},
{
"targets": [0],
"visible": false
}
],
});
$("#invoices tbody").on("click", "button", function () {
$("#menu-holder").html( '' );
var data = table.row( $(this).parents('tr') ).data();
$("#content-region").html( '' );
// Show popup form
var url = $( this ).data("url");
// now call ajax on this url
call_ajax( "/invoice/" + data[0] + "/action" );
modal.style.backgroundColor = null;
modal.style.display = "none";
menu.style.display = "block";
});
$("#add-new-invoice").on("click", function () {
// Show popup form
var url = $( this ).data("url");
// now call ajax on this url
call_ajax( url );
modal.style.display = "block";
loader.style.display = "block";
})
span.onclick = function() {
loader.style.display = "none";
modal.style.display = "none";
menu.style.display = "none";
modal.style.backgroundColor = '#fefefe';
}
window.onclick = function(event) {
if (event.target === menu) {
loader.style.display = "none";
modal.style.display = "none";
menu.style.display = "none";
modal.style.backgroundColor = '#fefefe';
}
}
});
function call_ajax( url ) {
// ToDo make an actual ajax call to get the form data
$.get( url )
.done(
function( data ) {
$("#menu-holder").html( data );
}
)
.fail(
function() {
$("#menu-holder").html( "<p>error</p>" );
}
);
}
</script>
The ajax is called via a url of /invoices/data/ this calls this function in a controller:
public function dataSource(Request $request): array
{
return Invoice::dataSource( $request );
}
and this is the dataSource function in the Invoice model:
public static function dataSource( Request $request )
{
$sortColumns = array(
0 => 'id',
1 => 'number',
2 => 'ref_number',
3 => 'clients.title',
4 => 'date',
5 => 'amount',
6 => 'itemised',
7 => 'comment',
8 => 'editable',
9 => 'status',
10 => 'actions'
);
$query = self::select( 'invoices.*' );
$searchFields = ['comment', 'clients.title', 'number'];
$with = [];
[$query, $json] = getDataSourceQuery( $query, $request, $with, $sortColumns, $searchFields );
$invoices = $query->get(); // <- This is where the error happens
foreach ($invoices as $invoice) {
// Get active jobs for this client.
if ( $invoice->type === 'INV' ) {
$job = Job::find( $invoice->number );
$client = $job->client->title;
$refNumber = '<span class="font-extrabold text-pink-800 dark:text-pink-300">Job #' . sprintf('%07d', $invoice->number) . '</span>';
} elseif ( $invoice->type === 'CLI' ) {
$client = Client::find( $invoice->number )->title;
$refNumber = '<span class="font-extrabold text-green-800 dark:text-green-300">Client #' . sprintf('%07d', $invoice->number ) . '</span>';
} else {
$project = Project::find( $invoice->number );
$client = $project->client->title;
$refNumber = '<span class="font-extrabold text-purple-800 dark:text-purple-300">Project #' . sprintf('%07d', $invoice->number ) . '</span>';
}
$lWeight = '700';
$dWeight = '300';
$bg = 'transparent';
switch ( $invoice->status) {
default:
case 'DRAFT':
$colour = 'blue';
$link = false;
break;
case 'LEGACY':
$colour = 'gray';
$link = false;
break;
case 'DELETED':
case 'VOIDED':
$colour = 'red';
$link = true;
break;
case 'SUBMITTED':
$colour = 'orange';
$lWeight = '500';
$dWeight = '400';
$link = true;
break;
case 'AUTHORISED':
$colour = 'green';
$link = true;
break;
case 'PAID':
$colour = 'stone';
$bg = 'emerald-300';
$link = true;
break;
}
$status = '
<div class="text-center w-full h-full bg-' . $bg . '">';
if ( $link && !is_null( $invoice->xero_id ) ) {
$status .= '
<a target="_blank" href="https://go.xero.com/AccountsReceivable/Edit.aspx?InvoiceID=' . $invoice->xero_id . '">';
if (!is_null($invoice->xero_id)) {
$status .= '
<div class="is-xero"></div>';
}
}
$status .= '
<span class="font-extrabold bg-' . $bg . ' text-' . $colour . '-' . $lWeight . ' dark:text-' . $colour . '-' . $dWeight . '">' . $invoice->status . '</span>';
if ( $link && !is_null( $invoice->xero_id ) ) {
$status .= '
</a>';
}
$status .= '
</div>';
$countWork = JobWorkItem::where( 'invoice_id', $invoice->id )->count();
$countPurchase = JobPurchaseItem::where( 'invoice_id', $invoice->id )->count();
$json['data'][] = [
$invoice->id,
'<div class="w-full h-full bg-' . $bg . '">
<a href="/invoice/' . $invoice->id . '">
<span class="font-extrabold text-' . $colour . '-' . $lWeight . ' dark:text-' . $colour . '-' . $dWeight . '">
#' . $invoice->type . sprintf( '%07d', $invoice->id ) . '
</span>
</a>
</div>',
$refNumber,
$client,
date_format( $invoice->created_at, 'Y-m-d' ),
time_format( $invoice->hours + 0.0 ),
'£' . number_format( $invoice->amount, 2 ),
( $invoice->is_itemised ? 'yes' : 'no' ) . ' - ' . $countWork . 'w | ' . $countPurchase . 'p',
$invoice->comment,
$invoice->allowEdit() ? 'yes' : 'no',
$status,
];
}
return $json;
}
and the helper function which handles the query getDataSourceQuery:
/**
* #param Builder|HasManyThrough $query
* #param Request $request
* #param array $with
* #param array $sortColumns
* #param array $searchFields
* #return array
*/
function getDataSourceQuery(Builder|HasManyThrough $query,
Request $request,
array $with,
array $sortColumns,
array $searchFields
) :array
{
$count = $query->count();
$search = $request->query('search', array('value' => '', 'regex' => false));
$draw = $request->query('draw', 0);
$start = $request->query('start', 0);
$length = $request->query('length', 25);
$order = $request->query('order', [['column' => 0, 'dir' => 'asc']]);
$column = $sortColumns[$order[0]['column']];
$dir = $order[0]['dir'];
$filter = $search['value'];
if ( str_contains( $column, '.' ) ) {
$boom = explode('.', $column );
$table = $query->getModel()->getTable();
$query->join( $boom[0], $table . '.number', '=', $column );
}
if (!empty($filter)) {
foreach ( $searchFields as $index => $field ) {
if ( $index === 0 ) {
$query->where($field, 'like', '%' . $filter . '%');
} else {
$query->orWhere($field, 'like', '%' . $filter . '%');
}
}
}
$query->orderBy($column, $dir)
->with( $with )
->take($length)
->skip($start);
// $qry = $query->toSql();
// $bindings = $query->getBindings();
$json = array(
'draw' => $draw,
'recordsTotal' => $count,
'recordsFiltered' => $count,
'data' => [],
);
return [$query, $json];
}
This works on page loading but when i click the Client column (title) it is failing due to the invoice model not having a client title. Filtering by client_id will not sort the clients by alphabetical order as they are not entered in any specific order. The client model has only an id, a title and a contact email column.
So reiterating the question how can I define my query to search for the client's title and sort them alphabetically?
I should point out that overall and elsewhere within the app this is working but there are other columns on other tables which also reference elements in another model or in some instances a function to render the result and also break.
Any solution to referencing an element in another model or a function would be appreciated.
thanks
*** EDIT *** I've added in a join in the helper function (code above updated) I now get an empty result when i sort by clients title. The query created is select `invoices`.* from `invoices` inner join `clients` on `invoices`.`number` = `clients`.`title` order by `clients`.`title` asc limit 10 offset 0;
I found the issue in the join, I was comparing the client_id to the title and not the id for the client. All working now.
OK in plain PHP I use the following to pass data to a GET
file_get_contents('https://ws.mysite.com/some.svc/here?userID=' . $session_id . '&score=' . $percentilescore . '&assessmentID=' . $testID . '&assessmentTitle=some');
I now want to apply this same piece of code to my CI project.
This Is how I have tried.
private function getResults()
{
$score = $this->actions->getSentEmailCount();
$percentilescore = $percentile = $this->actions->getPercentile($score);
$testID = '134';
$percentile = $this->actions->getPercentile($score);
$time = $this->input->get('time');
$timemath = 60000;
$timeinmseconds = $time * $timemath;
$adddata = array(
'uniID' => '5',
'testName' => 'some',
'testID' => $testID,
'total' => '10',
'correct' => $score = $this->actions->getScore(),
'percent' => $score = $this->actions->getScore(),
'dateTaken' => date('Y-m-d H:i:s'),
'timeSpent' => $timeinmseconds,
'userID' => $session_id,
);
$this->actions->add_record($adddata);
return $this->load->view('client/results', $data);
file_get_contents('https://ws.mysite.com/123.svc/some?userID=' . $session_id . '&score=' . $percentilescore . '&assessmentID=' . $testID . '&assessmentTitle=some');
}
It is not posting the data any idea why and how I should do it in CI ?
return should be the last statement in the function because it's immediately ends execution of the current function. Just put file_get_contents before return:
file_get_contents('https://ws.mysite.com/123.svc/some?userID=' . $session_id . '&score=' . $percentilescore . '&assessmentID=' . $testID . '&assessmentTitle=some');
$this->actions->add_record($adddata);
return $this->load->view('client/results', $data);
I want to run explain on a query that is slow but I don't know how to view the raw sql so I can run it in phpmyadmin and debug it. Here is the function.
private function getAttImages($limit, $forumIds = 0, $fidsReverse = false, $topicIds = 0, $membersIds = 0, $order = 'attach_date', $sort = 'desc', $group = null)
{
$fids = '';
if ($forumIds)
{
$r = '';
if ($fidsReverse)
{
$r = ' NOT ';
}
if (is_array($forumIds))
{
$forumIds = implode(',', $forumIds);
}
$fids = ' AND forums_topics.forum_id ' . $r . ' IN (' . $forumIds . ')';
}
$tids = '';
if ($topicIds)
{
$tids = ' AND forums_topics.tid IN (' . $topicIds . ')';
}
$mids = '';
if ($membersIds)
{
$mids = ' AND core_attachments.attach_member_id IN (' . $membersIds . ')';
}
$whereT = array();
$joinsT = array();
$findInPosts = ' AND ' . \IPS\Db::i()->findInSet('queued', array('0'));
$joinsT[] = array(
'select' => 'forums_posts.*',
'from' => 'forums_posts',
'where' => array("forums_posts.pid=core_attachments_map.id2" . $findInPosts),
);
$findInTopics = ' AND ' . \IPS\Db::i()->findInSet('approved', array('1'));
$joinsT[] = array(
'select' => 'forums_topics.*',
'from' => 'forums_topics',
'where' => array("forums_topics.tid=forums_posts.topic_id" . $findInTopics . $fids . $tids),
);
$select = 'core_attachments.attach_id AS custom_data, core_attachments.*';
if ($group)
{
$select = 'core_attachments.attach_id AS custom_data, COUNT(attach_is_image) as cnt_images, SUM(attach_hits) as summ_attach_hits, core_attachments.*';
}
$joinsT[] = array(
'select' => $select,
'from' => 'core_attachments',
'where' => array('core_attachments.attach_is_image=1 AND core_attachments.attach_is_archived=0 AND core_attachments.attach_id=core_attachments_map.attachment_id' . $mids),
);
$joinsT[] = array( 'select' => 'core_members.member_id, core_members.member_group_id, core_members.mgroup_others, core_members.name, core_members.members_seo_name',
'from' => 'core_members',
'where' => array('core_attachments.attach_member_id=core_members.member_id' . $mids),
);
$joinsT[] = array( 'select' => 'core_permission_index.perm_id',
'from' => 'core_permission_index',
'where' => array("core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_topics.forum_id"),
);
$groupT = $group;
$whereT[] = array(
"core_attachments_map.location_key='forums_Forums' AND " .
\IPS\Db::i()->findInSet('perm_view', array_merge(array(\IPS\Member::loggedIn()->member_group_id), array_filter(explode(',', \IPS\Member::loggedIn()->mgroup_others)))) . " OR perm_view='*'" .
$fids . $tids . $mids
);
$table = new \IPS\Helpers\Table\Db(
'core_attachments_map',
\IPS\Http\Url::internal('app=core&module=system&controller=nbattachpictures', 'front', 'nbattachpictures'),
$whereT,
$groupT
);
$table->joins = $joinsT;
$table->sortBy = $order;
$table->sortDirection = $sort;
$table->limit = $limit;
$table->rowsTemplate = array(\IPS\Theme::i()->getTemplate('plugins', 'core', 'global'), 'nbAttachmentsBlocksRows');
$table->parsers = array(
'custom_data' => function( $val, $row )
{
return array(
'topic_data' => \IPS\Http\Url::internal("app=forums&module=forums&controller=topic&id={$row['tid']}", 'front', 'forums_topic', array($row['title_seo'])),
'summ_attach_hits' => $row['summ_attach_hits'],
'jewel' => $this->attachJewel($row['summ_attach_hits']),
);
},
);
return $table;
}
Anybody know how I can see the SQL query only that is produced by this function? email is better than echo as I want to grab query from live site.
You could var_dump($table) and write the result in an email using the native php mail function or write it in a log file (this option is better).
Is that framework open-source? Because I couldn't find any documentation about the class \IPS\Helpers\Table\Db. Probably there's a method in it to build the query, you could look for it at that class source code and put the result of that method into the email message or log file instead of var_dump the table.
I am using yii and creating a cart, by using id of product i need to check that id already exists or not , but i use in_array and array_key_exists but unable to solve it Here is my code of controller
public function actionCartupdateajax() {
//start yii session
$session = Yii::app()->session;
// get posted values
$id = isset($_POST['id']) ? $_POST['id'] : "";
$name = isset($_POST['name']) ? $_POST['name'] : "";
$price = isset($_POST['price']) ? $_POST['price'] : "";
$imgSrc = Yii::app()->request->baseUrl . '/images/icondeletecart.png';
/*
* check if the 'cart' session array was created
* if it is NOT, create the 'cart' session array
*/
if (!isset($session['cart_items']) || count($session['cart_items']) == 0) {
Yii::app()->session['cart_items'] = array();
}
/*
* Here is the proble
* check if the item is in the array, if it is, do not add
*/
if (in_array($id, Yii::app()->session['cart_items'])) {
echo 'alreadyadded';
} else {
Yii::app()->session['cart_items'] = $id;
echo '<li><strong>' . $name . '</strong><span>' . $price . '</span>'
. '<img src=' . $imgSrc . ' alt="No Image" class="imagedeletecart" id=' . $id . '></li>';
}
}
and the error in console is
in_array() expects parameter 2 to be array, string given
I think problem in next row:
Yii::app()->session['cart_items'] = $id;
After this code cart_items will be NOT array, but integer or string.
Clear session and try to change:
Yii::app()->session['cart_items'][] = $id;
And better use CHtml for generation html. It is cleaner. Like this:
echo CHtml::tag('li', array(/*attrs*/), 'content_here');
//your code
echo '<li><strong>' . $name . '</strong><span>' . $price . '</span>'
. '<img src=' . $imgSrc . ' alt="No Image" class="imagedeletecart" id=' . $id . '></li>';
//I propose this way(but you can use your version):
echo CHtml::tag(
'li',
array(),
CHtml::tag(
'strong',
array(),
'name'
) . CHtml::tag(
'span',
array(),
'price'
) . CHtml::image(
'src',
'alt',
array(
'class' => 'imagedeletecart',
'id' => 'id'
)
)
);
I am using a custom form decorator found at: http://code.google.com/p/digitalus-cms/source/browse/trunk/library/Digitalus/Form/Decorator/Composite.php?r=767
At the bottom of the file (line 70) is:
$output = '<div class="form_element">'
. $label
. $input
. $errors
. $desc
. '</div>';
I would like to make the DIV class dynamic and passed when I create the elements in my controller. Any built-in ZEND functions I use only modifies the LABEL or INPUT. Here's an example of my element creation:
$decorator = new Composite();
$this->addElement('text', 'start', array(
'label' => 'Start Number',
'required' => true,
'filters' => array('StringTrim'),
'validators' => array(
'alnum',
),
'decorators' => array($decorator)
));
Any ideas would be very much appreciated. Thanks for taking the time to look!
Now sure why all CSS classes are hardcoded, if you are allowed to change this current decorator just fix the render() method:
class Digitalus_Form_Decorator_Composite
{
/* ... */
public function render($content)
{
$element = $this->getElement();
if (!$element instanceof Zend_Form_Element) {
return $content;
}
if (null === $element->getView()) {
return $content;
}
$separator = $this->getSeparator();
$placement = $this->getPlacement();
$label = $this->buildLabel();
$input = $this->buildInput();
$errors = $this->buildErrors();
$desc = $this->buildDescription();
$output = '<div class="'.$this->getOption('class').'">'
. $label
. $input
. $errors
. $desc
. '</div>';
switch ($placement) {
case (self::PREPEND):
return $output . $separator . $content;
case (self::APPEND):
default:
return $content . $separator . $output;
}
}
/* ... */
}
And during element creation:
$element->setDecorators(array(
/* ... */
array(array('div'=>'Composite'), array('class' => 'my_class_name'))
/* ... */
)));
If you don't want to edit existing decorator, just extend it and override render() method...