This commit is contained in:
= 2021-05-01 13:53:43 +05:00
commit d6fe725919
654 changed files with 18106 additions and 8594 deletions

View File

@ -42,7 +42,7 @@ return [
*/
'url' => env('APP_URL', 'http://localhost'),
'cdn' => env('CDN_URL','http://cdn.orient.tm'),
/*
|--------------------------------------------------------------------------
| Application Timezone
@ -81,7 +81,7 @@ return [
|
*/
'locale' => 'en',
'locale' => 'ru',
/*
|--------------------------------------------------------------------------

View File

@ -80,7 +80,7 @@ return [
|
*/
'backendTimezone' => 'UTC',
'backendTimezone' => 'Asia/Ashgabat',
/*
|--------------------------------------------------------------------------
@ -396,7 +396,7 @@ return [
|
*/
'enableCsrfProtection' => env('ENABLE_CSRF', false),
'enableCsrfProtection' => env('ENABLE_CSRF', true),
/*
|--------------------------------------------------------------------------

View File

@ -13,7 +13,7 @@ return [
|
*/
'default' => env('LOG_CHANNEL', 'single'),
'default' => env('LOG_CHANNEL', 'daily'),
/*
|--------------------------------------------------------------------------

View File

@ -109,7 +109,7 @@ return [
|
*/
'cookie' => 'october_session',
'cookie' => 'orient_session',
/*
|--------------------------------------------------------------------------

View File

@ -234,8 +234,8 @@ class UpdateManager
$params = [
'core' => $this->getHash(),
'plugins' => serialize($versions),
'themes' => serialize($themes),
'plugins' => base64_encode(json_encode($versions)),
'themes' => base64_encode(json_encode($themes)),
'build' => $build,
'force' => $force
];
@ -590,8 +590,9 @@ class UpdateManager
{
$fileCode = $name . $hash;
$filePath = $this->getFilePath($fileCode);
$innerPath = str_replace('.', '/', strtolower($name));
if (!Zip::extract($filePath, plugins_path())) {
if (!Zip::extract($filePath, plugins_path($innerPath))) {
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
}
@ -632,8 +633,9 @@ class UpdateManager
{
$fileCode = $name . $hash;
$filePath = $this->getFilePath($fileCode);
$innerPath = str_replace('.', '-', strtolower($name));
if (!Zip::extract($filePath, themes_path())) {
if (!Zip::extract($filePath, themes_path($innerPath))) {
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
}
@ -903,13 +905,16 @@ class UpdateManager
$http->toFile($filePath);
});
if ($result->code != 200) {
throw new ApplicationException(File::get($filePath));
if (in_array($result->code, [301, 302])) {
if ($redirectUrl = array_get($result->info, 'redirect_url')) {
$result = Http::get($redirectUrl, function ($http) use ($postData, $filePath) {
$http->toFile($filePath);
});
}
}
if (md5_file($filePath) != $expectedHash) {
@unlink($filePath);
throw new ApplicationException(Lang::get('system::lang.server.file_corrupt'));
if ($result->code != 200) {
throw new ApplicationException(File::get($filePath));
}
}
@ -942,7 +947,7 @@ class UpdateManager
*/
protected function createServerUrl($uri)
{
$gateway = Config::get('cms.updateServer', 'http://gateway.octobercms.com/api');
$gateway = Config::get('cms.updateServer', 'https://gateway.octobercms.com/api');
if (substr($gateway, -1) != '/') {
$gateway .= '/';
}
@ -958,10 +963,10 @@ class UpdateManager
*/
protected function applyHttpAttributes($http, $postData)
{
$postData['protocol_version'] = '1.1';
$postData['protocol_version'] = '1.2';
$postData['client'] = 'october';
$postData['server'] = base64_encode(serialize([
$postData['server'] = base64_encode(json_encode([
'php' => PHP_VERSION,
'url' => Url::to('/'),
'since' => PluginVersion::orderBy('created_at')->value('created_at')

View File

@ -126,6 +126,8 @@ class VersionManager
*/
protected function applyPluginUpdate($code, $version, $details)
{
$version = $this->normalizeVersion($version);
list($comments, $scripts) = $this->extractScriptsAndComments($details);
/*
@ -291,13 +293,18 @@ class VersionManager
$versionInfo = [];
}
if ($versionInfo) {
uksort($versionInfo, function ($a, $b) {
return version_compare($a, $b);
});
// Sort result
uksort($versionInfo, function ($a, $b) {
return version_compare($a, $b);
});
$result = [];
foreach ($versionInfo as $version => $info) {
$result[$this->normalizeVersion($version)] = $info;
}
return $this->fileVersions[$code] = $versionInfo;
return $this->fileVersions[$code] = $result;
}
/**
@ -549,6 +556,11 @@ class VersionManager
return $this;
}
protected function normalizeVersion($version)
{
return ltrim((string) $version, 'v');
}
/**
* @param $details
*

View File

@ -18,10 +18,18 @@ final class SecurityPolicy implements SecurityPolicyInterface
* @var array List of forbidden methods.
*/
protected $blockedMethods = [
// \October\Rain\Extension\ExtendableTrait
'addDynamicMethod',
'addDynamicProperty',
// \October\Rain\Support\Traits\Emitter
'bindEvent',
'bindEventOnce',
// Eloquent & Halcyon data modification
'insert',
'update',
'delete',
];
/**

View File

@ -65,13 +65,21 @@ class Plugin extends PluginBase
// extend the post model
PostModel::extend(function($model) {
$model->belongsToMany['tags'] = [
$model->belongsToMany['tags_ru'] = [
'Bedard\BlogTags\Models\Tag',
'table' => 'bedard_blogtags_post_tag',
'order' => 'name'
'order' => 'name',
'conditions' => 'bedard_blogtags_tags.locale = "ru"'
];
$model->belongsToMany['tags_en'] = [
'Bedard\BlogTags\Models\Tag',
'table' => 'bedard_blogtags_post_tag',
'order' => 'name',
'conditions' => 'bedard_blogtags_tags.locale = "en"'
];
});
// extend the post form
PostsController::extendFormFields(function($form, $model, $context) {
if (!$model instanceof PostModel) {
@ -79,9 +87,17 @@ class Plugin extends PluginBase
}
$form->addSecondaryTabFields([
'tags' => [
'label' => 'bedard.blogtags::lang.form.label',
'tags_ru' => [
'label' => 'Tags ru',
'mode' => 'relation',
'dependsOn' => 'locale',
'tab' => 'rainlab.blog::lang.post.tab_categories',
'type' => 'taglist'
],
'tags_en' => [
'label' => 'Tags en',
'mode' => 'relation',
'dependsOn' => 'locale',
'tab' => 'rainlab.blog::lang.post.tab_categories',
'type' => 'taglist'
]

View File

@ -1,6 +1,7 @@
<?php namespace Bedard\BlogTags\Models;
use Config;
use Illuminate\Support\Facades\Log;
use Model;
use RainLab\Blog\Models\Post;
@ -66,6 +67,7 @@ class Tag extends Model
protected function setInitialSlug()
{
$this->slug = str_slug($this->name);
$this->locale = input('Post')['locale'];
}
/**
@ -91,4 +93,6 @@ class Tag extends Model
return $this->url = $controller->pageUrl($pageName, $params);
}
}

View File

@ -0,0 +1,19 @@
# MIT license
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.

View File

@ -0,0 +1,169 @@
<?php namespace Indikator\DevTools;
use System\Classes\PluginBase;
use System\Classes\SettingsManager;
use Event;
use Backend;
use BackendAuth;
use BackendMenu;
use Indikator\DevTools\Models\Settings as Tools;
use Db;
class Plugin extends PluginBase
{
public $elevated = true;
public function pluginDetails()
{
return [
'name' => 'indikator.devtools::lang.plugin.name',
'description' => 'indikator.devtools::lang.plugin.description',
'author' => 'indikator.devtools::lang.plugin.author',
'icon' => 'icon-wrench',
'homepage' => 'https://github.com/gergo85/oc-devtools'
];
}
public function registerSettings()
{
return [
'devtool' => [
'label' => 'indikator.devtools::lang.help.menu_label',
'description' => 'indikator.devtools::lang.help.menu_description',
'icon' => 'icon-wrench',
'class' => 'Indikator\DevTools\Models\Settings',
'category' => SettingsManager::CATEGORY_SYSTEM,
'permissions' => ['indikator.devtools.settings']
]
];
}
public function registerFormWidgets()
{
return [
'Indikator\DevTools\FormWidgets\Help' => [
'label' => 'Help',
'code' => 'help'
]
];
}
public function registerPermissions()
{
return [
'indikator.devtools.editor' => [
'tab' => 'indikator.devtools::lang.plugin.name',
'label' => 'indikator.devtools::lang.editor.permission',
'order' => 100,
'roles' => ['developer']
],
'indikator.devtools.settings' => [
'tab' => 'indikator.devtools::lang.plugin.name',
'label' => 'indikator.devtools::lang.help.permission',
'order' => 200,
'roles' => ['developer']
]
];
}
public function boot()
{
// Add new menu
BackendMenu::registerCallback(function($manager) {
$manager->registerMenuItems('Indikator.DevTools', [
'editor' => [
'label' => 'indikator.devtools::lang.editor.menu_label',
'url' => Backend::url('indikator/devtools/editor'),
'icon' => 'icon-file-code-o',
'iconSvg' => 'plugins/indikator/devtools/assets/images/devtools-icon.svg',
'permissions' => ['indikator.devtools.editor'],
'order' => 390,
'sideMenu' => [
'assets' => [
'label' => 'indikator.devtools::lang.editor.plugins',
'icon' => 'icon-cubes',
'url' => 'javascript:;',
'attributes' => ['data-menu-item' => 'assets'],
'counterLabel' => 'cms::lang.asset.unsaved_label',
'order' => 100
]
]
]
]);
});
// Add new features
Event::listen('backend.form.extendFields', function($form)
{
// Security check
if (!BackendAuth::check()) {
return;
}
// Help docs
if ($this->tools_enabled('help') && (get_class($form->config->model) == 'Cms\Classes\Page' || get_class($form->config->model) == 'Cms\Classes\Partial' || get_class($form->config->model) == 'Cms\Classes\Layout') || get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') {
if (get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') {
$content = 'php';
}
else {
$content = 'cms';
}
$form->addSecondaryTabFields([
'help' => [
'label' => '',
'tab' => 'indikator.devtools::lang.help.tab',
'type' => 'help',
'content' => $content
]
]);
return;
}
// Wysiwyg editor
if ($this->tools_enabled('wysiwyg') && get_class($form->config->model) == 'Cms\Classes\Content') {
foreach ($form->getFields() as $field) {
if (!empty($field->config['type']) && $field->config['type'] == 'codeeditor') {
$field->config['type'] = $field->config['widget'] = 'richeditor';
}
}
}
});
}
public function tools_enabled($name)
{
// Security check
if ($name != 'help' && $name != 'wysiwyg') {
return false;
}
// Is enabled
if (!Tools::get($name.'_enabled', false)) {
return false;
}
// My account
$admin = BackendAuth::getUser();
// Is superuser
if (Tools::get($name.'_superuser', false) && $admin->is_superuser == 1) {
return true;
}
// Is admin group
if (Tools::get($name.'_admingroup', false) > 0 && Db::table('backend_users_groups')->where(['user_id' => $admin->id, 'user_group_id' => Tools::get($name.'_admingroup', false)])->count() == 1) {
return true;
}
// Is current user
if (Tools::get($name.'_adminid', false) > 0 && $admin->id == Tools::get($name.'_adminid', false)) {
return true;
}
// Finish
return false;
}
}

View File

@ -0,0 +1,24 @@
# Developer Tools Plugin
It is a must-have plugin for you, if you use the October's Docs a lot or you want to use the build-in wysiwyg editor on the Content page.
## Main features
* __Edit directly plugins with the online code editor!__
* Add a Help tab to the Pages, Partials or Layouts pages.
* Replace the code editor to wysiwyg editor on the Content page.
* Show the PHP's configuration if you logged as Superuser.
* Set a different permissions for the above features.
## Available languages
* en - English
* hu - Magyar
## Location of plugin
You can find it in the back-end: __Settings > System > Developer Tools__
## PHP's configuration
If you like to get the PHP's configuration of your server, use the following URL: www.yourwebsite.com/phpinfo
## Installation
1. Go to the __Settings > Updates & Plugins__ page in the Backend.
1. Click on the __Install plugins__ button.
1. Type the __Developer Tools__ text in the search field.

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 512 512" height="512px" id="Слой_1" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="wrench"><g><path d="M512,256c0,141.391-114.609,255.992-256,255.992C114.609,511.984,0,397.383,0,256.008 c0-141.391,114.609-255.992,256-256C397.391,0.008,512,114.617,512,256z" fill="#FA6E51"/><path d="M302.195,209.852L112.727,399.32l110.516,110.516c10.734,1.367,21.648,2.156,32.758,2.156 c139.391-0.008,252.656-111.398,255.852-250.016L356.742,106.875l-78.773,78.766L302.195,209.852z" fill="#E8573F"/></g><g><path d="M405.531,156.766l-41.086,41.094c-6.703,6.703-23.188,1.086-36.813-12.523 C314,171.703,308.391,155.227,315.109,148.5l0,0l41.633-41.625c-5.984-1.688-12.297-2.57-18.82-2.578 c-38.555,0.008-69.789,31.234-69.789,69.773c0,8.43,1.5,16.523,4.242,23.984L112.914,357.5c-5.32,5.313-8.594,12.672-8.617,20.797 c0,16.234,13.172,29.398,29.406,29.406c8.133,0,15.484-3.281,20.797-8.617l0,0l159.461-159.438 c7.461,2.727,15.539,4.227,23.961,4.219c38.539,0,69.781-31.258,69.781-69.797C407.703,168.109,406.945,162.297,405.531,156.766z" fill="#F4F6F9" id="wrench_3_"/><path d="M122.078,378.328c0,6.422,5.219,11.625,11.625,11.625c6.43,0,11.633-5.203,11.633-11.625 s-5.203-11.633-11.633-11.633C127.297,366.695,122.078,371.906,122.078,378.328z" fill="#CBD0D8"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,706 @@
/*
* Scripts for the CMS page.
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var CmsPage = function() {
Base.call(this)
//
// Initialization
//
this.init()
}
CmsPage.prototype = Object.create(BaseProto)
CmsPage.prototype.constructor = CmsPage
CmsPage.prototype.init = function() {
$(document).ready(this.proxy(this.registerHandlers))
}
CmsPage.prototype.updateTemplateList = function(type) {
var $form = $('#cms-side-panel form[data-template-type='+type+']'),
templateList = type + 'List'
$form.request(templateList + '::onUpdate', {
complete: function() {
$('button[data-control=delete-template]', $form).trigger('oc.triggerOn.update')
}
})
}
CmsPage.prototype.registerHandlers = function() {
var $document = $(document),
$masterTabs = $('#cms-master-tabs')
$masterTabs.on('closed.oc.tab', this.proxy(this.onTabClosed))
$masterTabs.on('beforeClose.oc.tab', this.proxy(this.onBeforeTabClose))
$masterTabs.on('oc.beforeRequest', this.proxy(this.onBeforeRequest))
$masterTabs.on('shown.bs.tab', this.proxy(this.onTabShown))
$masterTabs.on('initTab.oc.tab', this.proxy(this.onInitTab))
$masterTabs.on('afterAllClosed.oc.tab', this.proxy(this.onAfterAllTabsClosed))
$(window).on('ajaxInvalidField', this.proxy(this.ajaxInvalidField))
$document.on('open.oc.list', '#cms-side-panel', this.proxy(this.onOpenDocument))
$document.on('ajaxUpdate', '[data-control=filelist], [data-control=assetlist]', this.proxy(this.onAjaxUpdate))
$document.on('ajaxError', '#cms-master-tabs form', this.proxy(this.onAjaxError))
$document.on('ajaxSuccess', '#cms-master-tabs form', this.proxy(this.onAjaxSuccess))
$document.on('click', '#cms-side-panel form button[data-control=create-template], #cms-side-panel form li a[data-control=create-template]', this.proxy(this.onCreateTemplateClick))
$document.on('click', '#cms-side-panel form button[data-control=delete-template]', this.proxy(this.onDeleteTemplateClick))
$document.on('showing.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorShowing))
$document.on('hidden.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHidden))
$document.on('hiding.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHiding))
$document.on('click', '#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist a.remove', this.proxy(this.onComponentRemove))
$document.on('click', '#cms-component-list [data-component]', this.proxy(this.onComponentClick))
}
// EVENT HANDLERS
// ============================
CmsPage.prototype.onOpenDocument = function(event) {
/*
* Open a document when it's clicked in the sidebar
*/
var $item = $(event.relatedTarget),
$form = $item.closest('[data-template-type]'),
data = {
type: $form.data('template-type'),
theme: $item.data('item-theme'),
path: $item.data('item-path')
},
tabId = data.type + '-' + data.theme + '-' + data.path
if (data.type == 'asset' && $item.data('editable') === undefined)
return true
if ($form.length == 0)
return false
/*
* Find if the tab is already opened
*/
if ($('#cms-master-tabs').data('oc.tab').goTo(tabId))
return false
/*
* Open a new tab
*/
$.oc.stripeLoadIndicator.show()
$form.request('onOpenTemplate', {
data: data
}).done(function(data) {
$.oc.stripeLoadIndicator.hide()
var fileType = data.tabTitle.split('.').pop()
if (fileType == 'php' || fileType == 'js' || fileType == 'css' || fileType == 'html' || fileType == 'htm') fileType = 'icon-file-code-o'
else fileType = 'icon-file-text-o'
$('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, fileType)
}).always(function() {
$.oc.stripeLoadIndicator.hide()
}).fail(function(jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
$.oc.stripeLoadIndicator.hide()
})
return false
}
CmsPage.prototype.ajaxInvalidField = function(ev, element, name, messages, isFirst) {
/*
* Detect invalid fields, uncollapse the panel
*/
if (!isFirst)
return
ev.preventDefault()
var $el = $(element),
$panel = $el.closest('.form-tabless-fields.collapsed'),
$primaryPanel = $el.closest('.control-tabs.primary-tabs.collapsed')
if ($panel.length > 0)
$panel.removeClass('collapsed')
if ($primaryPanel.length > 0) {
$primaryPanel.removeClass('collapsed')
var pane = $primaryPanel.closest('.tab-pane'),
$secondaryPanel = $('.control-tabs.secondary-tabs', pane)
$secondaryPanel.removeClass('primary-collapsed')
}
$el.focus()
}
CmsPage.prototype.onTabClosed = function(ev) {
this.updateModifiedCounter()
if ($('> div.tab-content > div.tab-pane', '#cms-master-tabs').length == 0)
this.setPageTitle('')
}
CmsPage.prototype.onBeforeTabClose = function(ev) {
if ($.fn.table !== undefined)
$('[data-control=table]', ev.relatedTarget).table('dispose')
$.oc.foundation.controlUtils.disposeControls(ev.relatedTarget.get(0))
}
CmsPage.prototype.onBeforeRequest = function(ev) {
var $form = $(ev.target)
if ($('.components .layout-cell.error-component', $form).length > 0) {
if (!confirm('The form contains unknown components. Their properties will be lost on save. Do you want to save the form?'))
ev.preventDefault()
}
}
CmsPage.prototype.onTabShown = function(ev) {
/*
* Listen for the tabs "shown" event to track the current template in the list
*/
var $target = $(ev.target)
if ($target.closest('[data-control=tab]').attr('id') != 'cms-master-tabs')
return
var dataId = $target.closest('li').attr('data-tab-id'),
title = $target.attr('title'),
$sidePanel = $('#cms-side-panel')
if (title)
this.setPageTitle(title)
$sidePanel.find('[data-control=filelist]').fileList('markActive', dataId)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId])
}
CmsPage.prototype.onInitTab = function(ev, data) {
/*
* Listen for the tabs "initTab" event to inject extra controls to the tab
*/
if ($(ev.target).attr('id') != 'cms-master-tabs')
return
var $collapseIcon = $('<a href="javascript:;" class="tab-collapse-icon tabless"><i class="icon-chevron-up"></i></a>'),
$panel = $('.form-tabless-fields', data.pane)
$panel.append($collapseIcon);
$collapseIcon.click(function(){
$panel.toggleClass('collapsed')
if (typeof(localStorage) !== 'undefined')
localStorage.ocCmsTablessCollapsed = $panel.hasClass('collapsed') ? 1 : 0
window.setTimeout(function(){
$(window).trigger('oc.updateUi')
}, 500)
return false
})
var $primaryCollapseIcon = $('<a href="javascript:;" class="tab-collapse-icon primary"><i class="icon-chevron-down"></i></a>'),
$primaryPanel = $('.control-tabs.primary-tabs', data.pane),
$secondaryPanel = $('.control-tabs.secondary-tabs', data.pane)
if ($primaryPanel.length > 0) {
$secondaryPanel.append($primaryCollapseIcon);
$primaryCollapseIcon.click(function(){
$primaryPanel.toggleClass('collapsed')
$secondaryPanel.toggleClass('primary-collapsed')
$(window).trigger('oc.updateUi')
if (typeof(localStorage) !== 'undefined')
localStorage.ocCmsPrimaryCollapsed = $primaryPanel.hasClass('collapsed') ? 1 : 0
return false
})
}
if (typeof(localStorage) !== 'undefined') {
if (!$('a', data.tab).hasClass('new-template') && localStorage.ocCmsTablessCollapsed == 1)
$panel.addClass('collapsed')
if (localStorage.ocCmsPrimaryCollapsed == 1) {
$primaryPanel.addClass('collapsed')
$secondaryPanel.addClass('primary-collapsed')
}
}
var $componentListFormGroup = $('.control-componentlist', data.pane).closest('.form-group')
if ($primaryPanel.length > 0)
$primaryPanel.before($componentListFormGroup)
else
$secondaryPanel.parent().before($componentListFormGroup)
$componentListFormGroup.removeClass()
$componentListFormGroup.addClass('layout-row min-size')
this.updateComponentListClass(data.pane)
this.updateFormEditorMode(data.pane, true)
var $form = $('form', data.pane),
self = this
$form.on('changed.oc.changeMonitor', function() {
$panel.trigger('modified.oc.tab')
self.updateModifiedCounter()
})
$form.on('unchanged.oc.changeMonitor', function() {
$panel.trigger('unmodified.oc.tab')
self.updateModifiedCounter()
})
this.addTokenExpanderToEditor(data.pane, $form)
}
CmsPage.prototype.onAfterAllTabsClosed = function(ev) {
var $sidePanel = $('#cms-side-panel')
$sidePanel.find('[data-control=filelist]').fileList('markActive', null)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [null])
}
CmsPage.prototype.onAjaxUpdate = function(ev) {
var dataId = $('#cms-master-tabs .nav-tabs li.active').attr('data-tab-id'),
$sidePanel = $('#cms-side-panel')
$sidePanel.find('[data-control=filelist]').fileList('markActive', dataId)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId])
}
CmsPage.prototype.onAjaxSuccess = function(ev, context, data) {
var element = ev.target
if (data.templatePath !== undefined) {
$('input[name=templatePath]', element).val(data.templatePath)
$('input[name=templateMtime]', element).val(data.templateMtime)
$('[data-control=delete-button]', element).removeClass('hide')
$('[data-control=preview-button]', element).removeClass('hide')
if (data.pageUrl !== undefined)
$('[data-control=preview-button]', element).attr('href', data.pageUrl)
}
if (data.tabTitle !== undefined) {
$('#cms-master-tabs').ocTab('updateTitle', $(element).closest('.tab-pane'), data.tabTitle)
this.setPageTitle(data.tabTitle)
}
var tabId = $('input[name=templateType]', element).val() + '-'
+ $('input[name=theme]', element).val() + '-'
+ $('input[name=templatePath]', element).val();
$('#cms-master-tabs').ocTab('updateIdentifier', $(element).closest('.tab-pane'), tabId)
var templateType = $('input[name=templateType]', element).val()
if (templateType.length > 0) {
$.oc.cmsPage.updateTemplateList(templateType)
if (templateType == 'layout')
this.updateLayouts(element)
}
this.updateFormEditorMode($(element).closest('.tab-pane'), false)
if (context.handler == 'onSave' && (!data['X_OCTOBER_ERROR_FIELDS'] && !data['X_OCTOBER_ERROR_MESSAGE'])) {
$(element).trigger('unchange.oc.changeMonitor')
}
}
CmsPage.prototype.onAjaxError = function(ev, context, message, data, jqXHR) {
if (context.handler == 'onSave') {
if (jqXHR.responseText == 'mtime-mismatch') {
ev.preventDefault()
this.handleMtimeMismatch(ev.target)
}
}
}
CmsPage.prototype.onCreateTemplateClick = function(ev) {
var $form = $(ev.target).closest('[data-template-type]'),
type = $form.data('template-type'),
tabId = type + Math.random(),
self = this
$.oc.stripeLoadIndicator.show()
$form.request('onCreateTemplate', {
data: {type: type}
}).done(function(data) {
$('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon') + ' new-template')
$('#layout-side-panel').trigger('close.oc.sidePanel')
self.setPageTitle(data.tabTitle)
}).always(function(){
$.oc.stripeLoadIndicator.hide()
})
}
CmsPage.prototype.onDeleteTemplateClick = function(ev) {
var $el = $(ev.currentTarget),
$form = $el.closest('form'),
templateType = $form.data('template-type'),
self = this
if (!confirm($el.data('confirmation')))
return
$.oc.stripeLoadIndicator.show()
$form.request('onDeleteTemplates', {
data: {type: templateType}
}).done(function(data) {
var tabs = $('#cms-master-tabs').data('oc.tab');
$.each(data.deleted, function(index, path){
var
tabId = templateType + '-' + data.theme + '-' + path,
tab = tabs.findByIdentifier(tabId)
$('#cms-master-tabs').ocTab('closeTab', tab, true)
})
if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length)
$.oc.flashMsg({text: data.error, 'class': 'error'})
}).always(function(){
self.updateTemplateList(templateType)
$.oc.stripeLoadIndicator.hide()
})
}
CmsPage.prototype.onInspectorShowing = function(ev, data) {
$(ev.currentTarget).closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement(ev.currentTarget, data.callback)
ev.stopPropagation()
}
CmsPage.prototype.onInspectorHidden = function(ev) {
var element = ev.target,
values = $.parseJSON($('[data-inspector-values]', element).val())
$('[name="component_aliases[]"]', element).val(values['oc.alias'])
$('span.alias', element).text(values['oc.alias'])
}
CmsPage.prototype.onInspectorHiding = function(ev, values) {
var element = ev.target,
values = $.parseJSON($('[data-inspector-values]', element).val()),
alias = values['oc.alias'],
$componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout'),
$cell = $(ev.target).parent()
$('div.layout-cell', $componentList).each(function(){
if ($cell.get(0) == this)
return true
var $input = $('input[name="component_aliases[]"]', this)
if ($input.val() == alias) {
ev.preventDefault()
alert('The component alias "'+alias+'" is already used.')
return false
}
})
}
CmsPage.prototype.onComponentRemove = function(ev) {
var element = ev.currentTarget
$(element).trigger('change')
var pane = $(element).closest('.tab-pane'),
component = $(element).closest('div.layout-cell')
/*
* Remove any {% component %} tags in the editor for this component
*/
var editor = $('[data-control=codeeditor]', pane)
if (editor.length) {
var alias = $('input[name="component_aliases[]"]', component).val(),
codeEditor = editor.codeEditor('getEditorObject')
codeEditor.replace('', {
needle: "{% component '" + alias + "' %}"
})
}
component.remove()
$(window).trigger('oc.updateUi')
this.updateComponentListClass(pane)
return false
}
CmsPage.prototype.onComponentClick = function(ev) {
/*
* Determine if a page or layout is open in the master tabs
*/
var $componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout')
if ($componentList.length == 0) {
alert('Components can be added only to pages, partials and layouts.')
return;
}
var $component = $(ev.currentTarget).clone(),
$iconInput = $component.find('[data-component-icon]'),
$componentContainer = $('.layout-relative', $component),
$configInput = $component.find('[data-inspector-config]'),
$aliasInput = $component.find('[data-component-default-alias]'),
$valuesInput = $component.find('[data-inspector-values]'),
$nameInput = $component.find('[data-component-name]'),
$classInput = $component.find('[data-inspector-class]'),
alias = $aliasInput.val(),
originalAlias = alias,
counter = 2,
existingAliases = []
$('div.layout-cell input[name="component_aliases[]"]', $componentList).each(function(){
existingAliases.push($(this).val())
})
while($.inArray(alias, existingAliases) !== -1) {
alias = originalAlias + counter
counter++
}
// Set the last alias used so dragComponents can use it
$('input[name="component_aliases[]"]', $(ev.currentTarget)).val(alias)
$component.attr('data-component-attached', true)
$componentContainer.addClass($iconInput.val())
$iconInput.remove()
$componentContainer.attr({
'data-inspectable': '',
'data-inspector-title': $component.find('span.name').text(),
'data-inspector-description': $component.find('span.description').text(),
'data-inspector-config': $configInput.val(),
'data-inspector-class': $classInput.val()
})
$configInput.remove()
$('input[name="component_names[]"]', $component).val($nameInput.val())
$nameInput.remove()
$('input[name="component_aliases[]"]', $component).val(alias)
$component.find('span.alias').text(alias)
$valuesInput.val($valuesInput.val().replace('--alias--', alias))
$aliasInput.remove()
$component.addClass('adding')
$componentList.append($component)
$componentList.closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement($component)
$component.removeClass('adding')
$component.trigger('change')
this.updateComponentListClass($component.closest('.tab-pane'))
$(window).trigger('oc.updateUi')
}
// INTERNAL METHODS
// ============================
CmsPage.prototype.updateComponentListClass = function(pane) {
var $componentList = $('.control-componentlist', pane),
$primaryPanel = $('.control-tabs.primary-tabs', pane),
$primaryTabContainer = $('.nav-tabs', $primaryPanel),
hasComponents = $('.layout', $componentList).children(':not(.hidden)').length > 0
$primaryTabContainer.toggleClass('component-area', hasComponents)
$componentList.toggleClass('has-components', hasComponents)
}
CmsPage.prototype.updateFormEditorMode = function(pane, initialization) {
var $contentTypeElement = $('[data-toolbar-type]', pane)
if ($contentTypeElement.length == 0)
return
if ($contentTypeElement.data('toolbar-type') != 'content')
return
var fileName = $('input[name=fileName]', pane).val(),
parts = fileName.split('.'),
extension = 'txt',
mode = 'plain_text',
modes = { css: "css", htm: "html", html: "html", js: "javascript", less: "less", md: "markdown", sass: "sass", scss: "scss", txt: "plain_text", yaml: "yaml", php: "php" },
editor = $('[data-control=codeeditor]', pane)
if (parts.length >= 2)
extension = parts.pop().toLowerCase()
if (modes[extension] !== undefined)
mode = modes[extension];
var setEditorMode = function() {
window.setTimeout(function(){
editor.data('oc.codeEditor').editor.getSession().setMode({path: 'ace/mode/'+mode})
}, 200)
}
if (initialization)
editor.on('oc.codeEditorReady', setEditorMode)
else
setEditorMode()
}
CmsPage.prototype.updateModifiedCounter = function() {
var counters = {
page: { menu: 'pages', count: 0 },
partial: { menu: 'partials', count: 0 },
layout: { menu: 'layouts', count: 0 },
content: { menu: 'content', count: 0 },
asset: { menu: 'assets', count: 0}
}
$('> div.tab-content > div.tab-pane[data-modified]', '#cms-master-tabs').each(function(){
var inputType = $('> form > input[name=templateType]', this).val()
counters[inputType].count++
})
$.each(counters, function(type, data){
$.oc.sideNav.setCounter('cms/' + data.menu, data.count);
})
}
CmsPage.prototype.addTokenExpanderToEditor = function(pane, $form) {
var group = $('[data-field-name=markup]', pane),
editor = $('[data-control=codeeditor]', group),
canExpand = false,
self = this
if (!editor.length || editor.data('oc.tokenexpander'))
return
var toolbar = editor.codeEditor('getToolbar')
editor.tokenExpander()
var breakButton = $('<li />').prop({ 'class': 'tokenexpander-button' }).append(
$('<a />').prop({ 'href': 'javascript:; '}).append(
$('<i />').prop({ 'class': 'icon-code-fork' })
)
)
breakButton.hide().on('click', function(){
self.handleExpandToken(editor, $form)
return false
})
$('ul:first', toolbar).prepend(breakButton)
editor
.on('show.oc.tokenexpander', function(){
canExpand = true
breakButton.show()
})
.on('hide.oc.tokenexpander', function(){
canExpand = false
breakButton.hide()
})
.on('dblclick', function(ev){
if ((ev.metaKey || ev.ctrlKey) && canExpand) {
self.handleExpandToken(editor, $form)
}
})
}
CmsPage.prototype.handleExpandToken = function(editor, $form) {
editor.tokenExpander('expandToken', function(token, value){
return $form.request('onExpandMarkupToken', {
data: { tokenType: token, tokenName: value }
})
})
}
CmsPage.prototype.handleMtimeMismatch = function(form) {
var $form = $(form)
$form.popup({ handler: 'onOpenConcurrencyResolveForm' })
var popup = $form.data('oc.popup'),
self = this
$(popup.$content).on('click', 'button[data-action=reload]', function(){
popup.hide()
self.reloadForm(form)
})
$(popup.$content).on('click', 'button[data-action=save]', function(){
popup.hide()
$('input[name=templateForceSave]', $form).val(1)
$('a[data-request=onSave]', $form).trigger('click')
$('input[name=templateForceSave]', $form).val(0)
})
}
CmsPage.prototype.reloadForm = function(form) {
var
$form = $(form),
data = {
type: $('[name=templateType]', $form).val(),
theme: $('[name=theme]', $form).val(),
path: $('[name=templatePath]', $form).val(),
},
tabId = data.type + '-' + data.theme + '-' + data.path,
tabs = $('#cms-master-tabs').data('oc.tab'),
tab = tabs.findByIdentifier(tabId),
self = this
/*
* Update tab
*/
$.oc.stripeLoadIndicator.show()
$form.request('onOpenTemplate', {
data: data
}).done(function(data) {
$('#cms-master-tabs').ocTab('updateTab', tab, data.tabTitle, data.tab)
$('#cms-master-tabs').ocTab('unmodifyTab', tab)
self.updateModifiedCounter()
}).always(function() {
$.oc.stripeLoadIndicator.hide()
}).fail(function(jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
})
}
CmsPage.prototype.setPageTitle = function(title) {
if (title.length)
$.oc.layout.setPageTitle(title + ' | ')
else
$.oc.layout.setPageTitle(title)
}
CmsPage.prototype.updateLayouts = function(form) {
$(form).request('onGetTemplateList', {
success: function(data) {
$('#cms-master-tabs > .tab-content select[name="settings[layout]"]').each(function(){
var
$select = $(this),
value = $select.val()
$select.find('option').remove()
$.each(data.layouts, function(layoutFile, layoutName){
$select.append($('<option>').attr('value', layoutFile).text(layoutName))
})
$select.trigger('pause.oc.changeMonitor')
$select.val(value)
$select.trigger('change')
$select.trigger('resume.oc.changeMonitor')
})
}
})
}
$.oc.cmsPage = new CmsPage();
}(window.jQuery);

View File

@ -0,0 +1,288 @@
<?php namespace Indikator\DevTools\Classes;
use File;
use Lang;
use Config;
use Cms\Classes\Theme;
use Cms\Helpers\File as FileHelper;
use October\Rain\Extension\Extendable;
use ApplicationException;
use ValidationException;
/**
* The CMS asset file class.
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class Asset extends Extendable
{
/**
* @var \Cms\Classes\Theme A reference to the CMS theme containing the object.
*/
protected $theme;
/**
* @var string The plugin folder name.
*/
protected $dirName = 'plugins';
/**
* @var string Specifies the file name corresponding the CMS object.
*/
public $fileName;
/**
* @var string Specifies the file name, the CMS object was loaded from.
*/
protected $originalFileName = null;
/**
* @var string Last modified time.
*/
public $mtime;
/**
* @var string The entire file content.
*/
public $content;
/**
* @var array The attributes that are mass assignable.
*/
protected $fillable = [
'fileName',
'content'
];
/**
* @var array Allowable file extensions.
*/
protected $allowedExtensions = [];
/**
* @var bool Indicates if the model exists.
*/
public $exists = false;
/**
* Creates an instance of the object and associates it with a CMS theme.
* @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to.
*/
public function __construct()
{
$this->allowedExtensions = self::getEditableExtensions();
parent::__construct();
}
/**
* Loads the object from a file.
* This method is used in the CMS back-end. It doesn't use any caching.
* @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to.
* @param string $fileName Specifies the file name, with the extension.
* The file name can contain only alphanumeric symbols, dashes and dots.
* @return mixed Returns a CMS object instance or null if the object wasn't found.
*/
public static function load($theme, $fileName)
{
return (new static($theme))->find($fileName);
}
/**
* Prepares the theme datasource for the model.
* @param \Cms\Classes\Theme $theme Specifies a parent theme.
* @return $this
*/
public static function inTheme($theme)
{
if (is_string($theme)) {
$theme = Theme::load($theme);
}
return new static($theme);
}
/**
* Find a single template by its file name.
*
* @param string $fileName
* @return mixed|static
*/
public function find($fileName)
{
$filePath = $this->getFilePath($fileName);
if (!File::isFile($filePath)) {
return null;
}
if (($content = @File::get($filePath)) === false) {
return null;
}
$this->fileName = $fileName;
$this->originalFileName = $fileName;
$this->mtime = File::lastModified($filePath);
$this->content = $content;
$this->exists = true;
return $this;
}
/**
* Sets the object attributes.
* @param array $attributes A list of attributes to set.
*/
public function fill(array $attributes)
{
foreach ($attributes as $key => $value) {
if (!in_array($key, $this->fillable)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.invalid_property',
['name' => $key]
));
}
$this->$key = $value;
}
}
/**
* Saves the object to the disk.
*/
public function save()
{
$this->validateFileName();
$fullPath = $this->getFilePath();
if (File::isFile($fullPath) && $this->originalFileName !== $this->fileName) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.file_already_exists',
['name' => $this->fileName]
));
}
$dirPath = base_path().'/'.$this->dirName;
if (!file_exists($dirPath) || !is_dir($dirPath)) {
if (!File::makeDirectory($dirPath, 0777, true, true)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $dirPath]
));
}
}
if (($pos = strpos($this->fileName, '/')) !== false) {
$dirPath = dirname($fullPath);
if (!is_dir($dirPath) && !File::makeDirectory($dirPath, 0777, true, true)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $dirPath]
));
}
}
$newFullPath = $fullPath;
if (@File::put($fullPath, $this->content) === false) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_saving',
['name' => $this->fileName]
));
}
if (strlen($this->originalFileName) && $this->originalFileName !== $this->fileName) {
$fullPath = $this->getFilePath($this->originalFileName);
if (File::isFile($fullPath)) {
@unlink($fullPath);
}
}
clearstatcache();
$this->mtime = @File::lastModified($newFullPath);
$this->originalFileName = $this->fileName;
$this->exists = true;
}
/**
* Validate the supplied filename, extension and path.
* @param string $fileName
*/
protected function validateFileName($fileName = null)
{
if ($fileName === null) {
$fileName = $this->fileName;
}
$fileName = trim($fileName);
if (!strlen($fileName)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.file_name_required', [
'allowed' => implode(', ', $this->allowedExtensions),
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
])
]);
}
if (!FileHelper::validateExtension($fileName, $this->allowedExtensions, false)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.invalid_file_extension', [
'allowed' => implode(', ', $this->allowedExtensions),
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
])
]);
}
if (!FileHelper::validatePath($fileName, null)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.invalid_file', [
'name' => $fileName
])
]);
}
}
/**
* Returns the file name.
* @return string
*/
public function getFileName()
{
return $this->fileName;
}
/**
* Returns the absolute file path.
* @param string $fileName Specifies the file name to return the path to.
* @return string
*/
public function getFilePath($fileName = null)
{
if ($fileName === null) {
$fileName = $this->fileName;
}
return base_path().'/'.$this->dirName.'/'.$fileName;
}
/**
* Returns a list of editable asset extensions.
* The list can be overridden with the cms.editableAssetTypes configuration option.
* @return array
*/
public static function getEditableExtensions()
{
$defaultTypes = ['css', 'js', 'less', 'sass', 'scss', 'php', 'htm', 'html', 'yaml', 'md', 'txt'];
$configTypes = Config::get('cms.editableAssetTypes');
if (!$configTypes) {
return $defaultTypes;
}
return $configTypes;
}
}

View File

@ -0,0 +1,29 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
fileName:
label: cms::lang.editor.filename
attributes:
default-focus: 1
toolbar:
type: partial
path: content_toolbar
cssClass: collapse-visible
tabs:
cssClass: master-area
secondaryTabs:
stretch: true
fields:
content:
tab: cms::lang.editor.content
stretch: true
type: codeeditor

View File

@ -0,0 +1,8 @@
{
"name": "indikator/devtools-plugin",
"type": "october-plugin",
"description": "None",
"require": {
"composer/installers": "~1.0"
}
}

View File

@ -0,0 +1,380 @@
<?php namespace Indikator\DevTools\Controllers;
use Url;
use Lang;
use Flash;
use Config;
use Request;
use Response;
use Exception;
use BackendMenu;
use Indikator\DevTools\Classes\Asset;
use Indikator\DevTools\Widgets\AssetList;
use Cms\Classes\Router;
use Backend\Classes\Controller;
use Backend\Classes\WidgetManager;
use System\Helpers\DateTime;
use October\Rain\Router\Router as RainRouter;
use ApplicationException;
/**
* CMS index
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class Editor extends Controller
{
use \Backend\Traits\InspectableContainer;
public $requiredPermissions = ['indikator.devtools.editor'];
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Indikator.DevTools', 'editor', 'assets');
try {
new AssetList($this, 'assetList');
}
catch (Exception $ex) {
$this->handleError($ex);
}
}
//
// Pages
//
public function index()
{
$this->addJs('/plugins/indikator/devtools/assets/october.cmspage.js');
$this->addJs('/modules/cms/assets/js/october.dragcomponents.js', 'core');
$this->addJs('/modules/cms/assets/js/october.tokenexpander.js', 'core');
$this->addCss('/modules/cms/assets/css/october.components.css', 'core');
$this->bodyClass = 'compact-container';
$this->pageTitle = 'cms::lang.cms.menu_label';
$this->pageTitleTemplate = '%s '.trans($this->pageTitle);
if (Request::ajax() && Request::input('formWidgetAlias')) {
$this->bindFormWidgetToController();
}
}
public function index_onOpenTemplate()
{
$type = Request::input('type');
$template = $this->loadTemplate($type, Request::input('path'));
$widget = $this->makeTemplateFormWidget($type, $template);
$this->vars['templatePath'] = Request::input('path');
$this->vars['lastModified'] = DateTime::makeCarbon($template->mtime);
if ($type === 'page') {
$router = new RainRouter;
$this->vars['pageUrl'] = $router->urlFromPattern($template->url);
}
return [
'tabTitle' => $this->getTabTitle($type, $template),
'tab' => $this->makePartial('form_page', [
'form' => $widget,
'templateType' => $type,
'templateMtime' => $template->mtime
])
];
}
public function onSave()
{
$type = Request::input('templateType');
$templatePath = trim(Request::input('templatePath'));
$template = $templatePath ? $this->loadTemplate($type, $templatePath) : $this->createTemplate($type);
$formWidget = $this->makeTemplateFormWidget($type, $template);
$saveData = $formWidget->getSaveData();
$postData = post();
$templateData = [];
$settings = array_get($saveData, 'settings', []) + Request::input('settings', []);
$settings = $this->upgradeSettings($settings);
if ($settings) {
$templateData['settings'] = $settings;
}
$fields = ['markup', 'code', 'fileName', 'content'];
foreach ($fields as $field) {
if (array_key_exists($field, $saveData)) {
$templateData[$field] = $saveData[$field];
}
elseif (array_key_exists($field, $postData)) {
$templateData[$field] = $postData[$field];
}
}
if (!empty($templateData['markup']) && Config::get('cms.convertLineEndings', false) === true) {
$templateData['markup'] = $this->convertLineEndings($templateData['markup']);
}
if (!empty($templateData['code']) && Config::get('cms.convertLineEndings', false) === true) {
$templateData['code'] = $this->convertLineEndings($templateData['code']);
}
if (!Request::input('templateForceSave') && $template->mtime) {
if (Request::input('templateMtime') != $template->mtime) {
throw new ApplicationException('mtime-mismatch');
}
}
$template->attributes = [];
$template->fill($templateData);
$template->save();
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.save', [$template, $type]);
Flash::success(Lang::get('cms::lang.template.saved'));
$result = [
'templatePath' => $template->fileName,
'templateMtime' => $template->mtime,
'tabTitle' => $this->getTabTitle($type, $template)
];
return $result;
}
public function onOpenConcurrencyResolveForm()
{
return $this->makePartial('concurrency_resolve_form');
}
public function onCreateTemplate()
{
$type = Request::input('type');
$template = $this->createTemplate($type);
if ($type === 'asset') {
$template->fileName = $this->widget->assetList->getCurrentRelativePath();
}
$widget = $this->makeTemplateFormWidget($type, $template);
$this->vars['templatePath'] = '';
return [
'tabTitle' => $this->getTabTitle($type, $template),
'tab' => $this->makePartial('form_page', [
'form' => $widget,
'templateType' => $type,
'templateMtime' => null
])
];
}
public function onDeleteTemplates()
{
$type = Request::input('type');
$templates = Request::input('template');
$error = null;
$deleted = [];
try {
foreach ($templates as $path => $selected) {
if ($selected) {
$this->loadTemplate($type, $path)->delete();
$deleted[] = $path;
}
}
}
catch (Exception $ex) {
$error = $ex->getMessage();
}
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.delete', [$type]);
return [
'deleted' => $deleted,
'error' => $error,
'theme' => Request::input('theme')
];
}
public function onDelete()
{
$type = Request::input('templateType');
$this->loadTemplate($type, trim(Request::input('templatePath')))->delete();
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.delete', [$type]);
}
//
// Methods for the internal use
//
protected function resolveTypeClassName($type)
{
$types = [
'asset' => Asset::class
];
if (!array_key_exists($type, $types)) {
throw new ApplicationException(trans('cms::lang.template.invalid_type'));
}
return $types[$type];
}
protected function loadTemplate($type, $path)
{
$class = $this->resolveTypeClassName($type);
if (!($template = call_user_func([$class, 'load'], $this->theme, $path))) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.processSettingsAfterLoad', [$template]);
return $template;
}
protected function createTemplate($type)
{
$class = $this->resolveTypeClassName($type);
if (!($template = $class::inTheme($this->theme))) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
return $template;
}
protected function getTabTitle($type, $template)
{
if ($type === 'asset') {
$result = in_array($type, ['asset', 'content']) ? $template->getFileName() : $template->getBaseFileName();
if (!$result) {
$result = trans('cms::lang.'.$type.'.new');
}
return $result;
}
return $template->getFileName();
}
protected function makeTemplateFormWidget($type, $template, $alias = null)
{
$formConfigs = [
'asset' => '~/plugins/indikator/devtools/classes/asset/fields.yaml'
];
if (!array_key_exists($type, $formConfigs)) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
$widgetConfig = $this->makeConfig($formConfigs[$type]);
$widgetConfig->model = $template;
$widgetConfig->alias = $alias ?: 'form'.studly_case($type).md5($template->getFileName()).uniqid();
$widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig);
return $widget;
}
protected function upgradeSettings($settings)
{
/*
* Handle component usage
*/
$componentProperties = post('component_properties');
$componentNames = post('component_names');
$componentAliases = post('component_aliases');
if ($componentProperties !== null) {
if ($componentNames === null || $componentAliases === null) {
throw new ApplicationException(trans('cms::lang.component.invalid_request'));
}
$count = count($componentProperties);
if (count($componentNames) != $count || count($componentAliases) != $count) {
throw new ApplicationException(trans('cms::lang.component.invalid_request'));
}
for ($index = 0; $index < $count; $index++) {
$componentName = $componentNames[$index];
$componentAlias = $componentAliases[$index];
$section = $componentName;
if ($componentAlias != $componentName) {
$section .= ' '.$componentAlias;
}
$properties = json_decode($componentProperties[$index], true);
unset($properties['oc.alias']);
unset($properties['inspectorProperty']);
unset($properties['inspectorClassName']);
$settings[$section] = $properties;
}
}
/*
* Handle view bag
*/
$viewBag = post('viewBag');
if ($viewBag !== null) {
$settings['viewBag'] = $viewBag;
}
/*
* Extensibility
*/
$dataHolder = (object) ['settings' => $settings];
$this->fireSystemEvent('cms.template.processSettingsBeforeSave', [$dataHolder]);
return $dataHolder->settings;
}
protected function bindFormWidgetToController()
{
$alias = Request::input('formWidgetAlias');
$type = Request::input('templateType');
$object = $this->loadTemplate($type, Request::input('templatePath'));
$widget = $this->makeTemplateFormWidget($type, $object, $alias);
$widget->bindToController();
}
/**
* Replaces Windows style (/r/n) line endings with unix style (/n)
* line endings.
* @param string $markup The markup to convert to unix style endings
* @return string
*/
protected function convertLineEndings($markup)
{
$markup = str_replace(["\r\n", "\r"], "\n", $markup);
return $markup;
}
}

View File

@ -0,0 +1,29 @@
<?= Form::open(['onsubmit'=>'return false']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.form.concurrency_file_changed_title')) ?></h4>
</div>
<div class="modal-body">
<p><?= e(trans('backend::lang.form.concurrency_file_changed_description')) ?></p>
</div>
<div class="modal-footer">
<button
type="submit"
data-action="reload"
class="btn btn-primary">
<?= e(trans('backend::lang.form.reload')) ?>
</button>
<button
type="submit"
data-action="save"
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,27 @@
<div class="form-buttons loading-indicator-container" data-toolbar-type="content">
<a
href="javascript:;"
class="btn btn-primary oc-icon-check save"
data-request="onSave"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
data-hotkey="ctrl+s, cmd+s">
<?= e(trans('backend::lang.form.save')) ?>
</a>
<button
type="button"
class="btn btn-danger empty oc-icon-trash-o <?php if (!$templatePath): ?>hide<?php endif ?>"
data-request="onDelete"
data-request-confirm="<?= e(trans('cms::lang.content.delete_confirm_single')) ?>"
data-request-success="$.oc.cmsPage.updateTemplateList('content'); $(this).trigger('close.oc.tab', [{force: true}])"
data-control="delete-button"></button>
<?php if (isset($lastModified)): ?>
<span
class="btn empty oc-icon-calendar"
title="<?= e(trans('backend::lang.media.last_modified')) ?>: <?= $lastModified ?>"
data-toggle="tooltip"
data-placement="right">
</span>
<?php endif; ?>
</div>

View File

@ -0,0 +1,15 @@
<?= Form::open([
'class' => 'layout',
'data-change-monitor' => 'true',
'data-window-close-confirm' => e(trans('backend::lang.form.confirm_tab_close')),
'data-inspector-external-parameters' => true
]) ?>
<?= $form->render() ?>
<input type="hidden" value="<?= e($form->alias) ?>" name="formWidgetAlias">
<input type="hidden" value="<?= ($templateType) ?>" name="templateType">
<input type="hidden" value="<?= ($templatePath) ?>" name="templatePath">
<input type="hidden" value="<?= ($templateMtime) ?>" name="templateMtime">
<input type="hidden" value="0" name="templateForceSave">
<?= Form::close() ?>

View File

@ -0,0 +1,14 @@
<div class="layout control-scrollpanel" id="cms-side-panel">
<div class="layout-cell">
<div class="layout-relative fix-button-container">
<form
class="layout"
data-content-id="assets"
data-template-type="asset"
data-type-icon="oc-icon-cubes"
onsubmit="return false">
<?= $this->widget->assetList->render() ?>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
<?= Block::put('sidepanel') ?>
<?php if (!$this->fatalError): ?>
<?= $this->makePartial('sidepanel') ?>
<?php endif ?>
<?= Block::endPut() ?>
<?= Block::put('body') ?>
<?php if (!$this->fatalError): ?>
<div
data-control="tab"
data-closable
data-close-confirmation="<?= e(trans('backend::lang.form.confirm_tab_close')) ?>"
data-pane-classes="layout-cell"
data-max-title-symbols="15"
data-title-as-file-names="true"
class="layout control-tabs master-tabs fancy-layout oc-logo-transparent"
id="cms-master-tabs">
<div class="layout-row min-size">
<div class="tabs-container">
<ul class="nav nav-tabs"></ul>
</div>
</div>
<div class="tab-content layout-row"></div>
</div>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<?php endif ?>
<?= Block::endPut() ?>

View File

@ -0,0 +1,30 @@
<?php namespace Indikator\DevTools\FormWidgets;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
class Help extends FormWidgetBase
{
protected $defaultAlias = 'help';
public $content = 'cms';
public function init()
{
$this->fillFromConfig([
'content'
]);
}
public function render()
{
$this->prepareVars();
return $this->makePartial('help');
}
protected function prepareVars()
{
$this->vars['content'] = $this->content;
}
}

View File

@ -0,0 +1,88 @@
<?php if ($content == 'cms'): ?>
<div class="row" style="padding:24px;">
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.cms')) ?></strong><br>
<a href="https://octobercms.com/docs/cms/pages#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.pages')) ?></a><br>
<a href="https://octobercms.com/docs/cms/partials#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.partials')) ?></a><br>
<a href="https://octobercms.com/docs/cms/layouts#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.layouts')) ?></a><br>
<a href="https://octobercms.com/docs/cms/content#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.content')) ?></a><br>
<a href="https://octobercms.com/docs/cms/ajax#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.ajax')) ?></a><br>
<br>
<strong><?= e(trans('indikator.devtools::lang.help.functions')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/function-str#search-input" target="_blank">str()</a><br>
<a href="https://octobercms.com/docs/markup/function-form#search-input" target="_blank">form()</a><br>
<a href="https://octobercms.com/docs/markup/function-html#search-input" target="_blank">html()</a><br>
<a href="https://octobercms.com/docs/markup/function-dump#search-input" target="_blank">dump()</a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.tags')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/tag-partial#search-input" target="_blank">{% partial %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-content#search-input" target="_blank">{% content %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-component#search-input" target="_blank">{% component %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-placeholder#search-input" target="_blank">{% placeholder %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-scripts#search-input" target="_blank">{% scripts %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-styles#search-input" target="_blank">{% styles %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-flash#search-input" target="_blank">{% flash %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-verbatim#search-input" target="_blank">{% verbatim %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-for#search-input" target="_blank">{% for %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-if#search-input" target="_blank">{% if %}</a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.filters')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/filter-app#search-input" target="_blank">|app</a><br>
<a href="https://octobercms.com/docs/markup/filter-page#search-input" target="_blank">|page</a><br>
<a href="https://octobercms.com/docs/markup/filter-theme#search-input" target="_blank">|theme</a><br>
<a href="https://octobercms.com/docs/markup/filter-media#search-input" target="_blank">|media</a><br>
<a href="https://octobercms.com/docs/markup/filter-md#search-input" target="_blank">|md</a><br>
<a href="https://octobercms.com/docs/markup/filter-raw#search-input" target="_blank">|raw</a><br>
<a href="https://octobercms.com/docs/markup/filter-default#search-input" target="_blank">|default</a><br>
<br>
<strong><?= e(trans('indikator.devtools::lang.help.database')) ?></strong><br>
<a href="https://octobercms.com/docs/database/basics#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.basic')) ?></a><br>
<a href="https://octobercms.com/docs/database/query#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.queries')) ?></a>
</div>
</div>
<?php else: ?>
<div class="row" style="padding:24px;">
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.plugins')) ?></strong><br>
<a href="https://octobercms.com/docs/plugin/registration#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.registration')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/updates#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.version_history')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/components#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.building_components')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/settings#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.settings_config')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/localization#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.localization')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/scheduling#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.task_scheduling')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/extending#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.extending_plugins')) ?></a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.backend')) ?></strong><br>
<a href="https://octobercms.com/docs/backend/controllers-ajax#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.controllers_ajax')) ?></a><br>
<a href="https://octobercms.com/docs/backend/views-partials#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.views_partials')) ?></a><br>
<a href="https://octobercms.com/docs/backend/widgets#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.widgets')) ?></a><br>
<a href="https://octobercms.com/docs/backend/forms#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.forms')) ?></a><br>
<a href="https://octobercms.com/docs/backend/lists#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.lists')) ?></a><br>
<a href="https://octobercms.com/docs/backend/relations#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.relations')) ?></a><br>
<a href="https://octobercms.com/docs/backend/reorder#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.sorting_records')) ?></a><br>
<a href="https://octobercms.com/docs/backend/import-export#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.importing_exporting')) ?></a><br>
<a href="https://octobercms.com/docs/backend/users#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.users_permissions')) ?></a><br>
<a href="https://octobercms.com/docs/ui#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.user_interface_guide')) ?></a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.services')) ?></strong><br>
<a href="https://octobercms.com/docs/services/application#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.application')) ?></a><br>
<a href="https://octobercms.com/docs/services/behaviors#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.behaviors')) ?></a><br>
<a href="https://octobercms.com/docs/services/events#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.events')) ?></a><br>
<a href="https://octobercms.com/docs/services/html#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.forms_html')) ?></a><br>
<a href="https://octobercms.com/docs/services/mail#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.mail')) ?></a><br>
<a href="https://octobercms.com/docs/services/request-input#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.request_input')) ?></a><br>
<a href="https://octobercms.com/docs/services/response-view#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.response_view')) ?></a><br>
<a href="https://octobercms.com/docs/services/router#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.router')) ?></a><br>
<a href="https://octobercms.com/docs/services/session#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.session')) ?></a><br>
<a href="https://octobercms.com/docs/services/validation#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.validation')) ?></a>
</div>
</div>
<?php endif; ?>

View File

@ -0,0 +1,72 @@
<?php
return [
'plugin' => [
'name' => 'Developer Tools',
'description' => 'Useful features for developers.',
'author' => 'Gergő Szabó'
],
'editor' => [
'menu_label' => 'Code editor',
'plugins' => 'Plugins',
'permission' => 'Use the code editor'
],
'help' => [
'menu_label' => 'Developer Tools',
'menu_description' => 'Settings of developer features.',
'tab' => 'Help',
'cms' => 'CMS',
'pages' => 'Pages',
'partials' => 'Partials',
'layouts' => 'Layouts',
'content' => 'Content',
'ajax' => 'AJAX',
'functions' => 'Functions',
'tags' => 'Tags',
'filters' => 'Filters',
'database' => 'Database',
'basic' => 'Basic',
'queries' => 'Queries',
'plugins' => 'Plugins',
'registration' => 'Registration',
'version_history' => 'Version history',
'building_components' => 'Building components',
'settings_config' => 'Settings & Config',
'localization' => 'Localization',
'task_scheduling' => 'Task scheduling',
'extending_plugins' => 'Extending plugins',
'backend' => 'Backend',
'controllers_ajax' => 'Controllers & AJAX',
'views_partials' => 'Views & Partials',
'widgets' => 'Widgets',
'forms' => 'Forms',
'lists' => 'Lists',
'relations' => 'Relations',
'sorting_records' => 'Sorting records',
'importing_exporting' => 'Importing & Exporting',
'users_permissions' => 'Users & Permissions',
'user_interface_guide' => 'User interface guide',
'services' => 'Services',
'application' => 'Application',
'behaviors' => 'Behaviors',
'events' => 'Events',
'forms_html' => 'Forms & Html',
'mail' => 'Mail',
'request_input' => 'Request & Input',
'response_view' => 'Response & View',
'router' => 'Router',
'session' => 'Session',
'validation' => 'Validation',
'permission' => 'Manage settings'
],
'form' => [
'wysiwyg_label' => 'Wysiwyg Editor',
'wysiwyg_enabled' => 'Enabled on the Content page',
'help_label' => 'Help',
'help_enabled' => 'Enable the Help tab on the CMS pages',
'select_none' => '-- None --',
'select_superuser' => 'Show it for the Superusers',
'select_admingroup' => 'Show it for the following admin group',
'select_adminid' => 'Show it for the following administrator',
]
];

View File

@ -0,0 +1,72 @@
<?php
return [
'plugin' => [
'name' => 'Fejlesztői eszközök',
'description' => 'Hasznos szolgáltatások fejlesztőknek.',
'author' => 'Szabó Gergő'
],
'editor' => [
'menu_label' => 'Kódszerkesztő',
'plugins' => 'Bővítmények',
'permission' => 'Szerkesztő használata'
],
'help' => [
'menu_label' => 'Fejlesztőknek',
'menu_description' => 'Szolgáltatások és lehetőségek beállítása.',
'tab' => 'Súgó',
'cms' => 'Testreszabás',
'pages' => 'Lapok',
'partials' => 'Részlapok',
'layouts' => 'Elrendezések',
'content' => 'Tartalom',
'ajax' => 'AJAX',
'functions' => 'Függvények',
'tags' => 'Tag-ek',
'filters' => 'Filterek',
'database' => 'Adatbázis',
'basic' => 'Alapok',
'queries' => 'Lekérdezések',
'plugins' => 'Bővítmények',
'registration' => 'Regisztrálás',
'version_history' => 'Verzió előzmények',
'building_components' => 'Komponensek',
'settings_config' => 'Beállítások',
'localization' => 'Többnyelvűsítés',
'task_scheduling' => 'Időzítés',
'extending_plugins' => 'Kiegészítés',
'backend' => 'Admin',
'controllers_ajax' => 'Kontroller és AJAX',
'views_partials' => 'Részlapok',
'widgets' => 'Widgetek',
'forms' => 'Űrlapok',
'lists' => 'Listák',
'relations' => 'Kapcsolatok',
'sorting_records' => 'Elemek rendezése',
'importing_exporting' => 'Import és export',
'users_permissions' => 'Jogosultságok',
'user_interface_guide' => 'Felhasználói felület',
'services' => 'Szolgáltatások',
'application' => 'Alkalmazás',
'behaviors' => 'Viselkedések',
'events' => 'Események',
'forms_html' => 'Űrlapok és HTML',
'mail' => 'Levelezés',
'request_input' => 'Űrlap értékek',
'response_view' => 'Válasz megjelenítés',
'router' => 'Router kezelés',
'session' => 'Munkamenet',
'validation' => 'Ellenőrzés',
'permission' => 'Beállítások kezelése'
],
'form' => [
'wysiwyg_label' => 'Szövegszerkesztő',
'wysiwyg_enabled' => 'Engedélyezés a Tartalom oldalon',
'help_label' => 'Súgó',
'help_enabled' => 'Engedélyezés a Testreszabás aloldalakon',
'select_none' => '-- nincs --',
'select_superuser' => 'Szuperadminok láthatják',
'select_admingroup' => 'A következő admin csoport tagjai láthatják',
'select_adminid' => 'A következő admin felhasználó láthatja'
]
];

View File

@ -0,0 +1,63 @@
<?php namespace Indikator\DevTools\Models;
use Model;
use Db;
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'indikator_devtools_settings';
public $settingsFields = 'fields.yaml';
public $selectList = [0 => 'indikator.devtools::lang.form.select_none'];
public function getHelpAdmingroupOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_user_groups')->orderBy('name', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->name.' ('.Db::table('backend_users_groups')->where('user_group_id', $item->id)->count().')';
}
return $result;
}
public function getHelpAdminidOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_users')->orderBy('login', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->login.' ('.$item->email.')';
}
return $result;
}
public function getWysiwygAdmingroupOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_user_groups')->orderBy('name', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->name.' ('.Db::table('backend_users_groups')->where('user_group_id', $item->id)->count().')';
}
return $result;
}
public function getWysiwygAdminidOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_users')->orderBy('login', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->login.' ('.$item->email.')';
}
return $result;
}
}

View File

@ -0,0 +1,85 @@
# ===================================
# Field Definitions
# ===================================
fields:
help_title:
label: indikator.devtools::lang.form.help_label
type: section
comment: ''
span: left
wysiwyg_title:
label: indikator.devtools::lang.form.wysiwyg_label
type: section
comment: ''
span: right
help_enabled:
label: indikator.devtools::lang.form.help_enabled
type: switch
default: false
span: left
wysiwyg_enabled:
label: indikator.devtools::lang.form.wysiwyg_enabled
type: switch
default: false
span: right
help_superuser:
label: indikator.devtools::lang.form.select_superuser
type: switch
default: false
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_superuser:
label: indikator.devtools::lang.form.select_superuser
type: switch
default: false
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right
help_admingroup:
label: indikator.devtools::lang.form.select_admingroup
type: dropdown
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_admingroup:
label: indikator.devtools::lang.form.select_admingroup
type: dropdown
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right
help_adminid:
label: indikator.devtools::lang.form.select_adminid
type: dropdown
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_adminid:
label: indikator.devtools::lang.form.select_adminid
type: dropdown
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right

View File

@ -0,0 +1,10 @@
<?php
Route::get('/phpinfo', function()
{
$admin = BackendAuth::getUser();
if (isset($admin) && $admin->is_superuser == 1) {
echo phpinfo();
}
});

View File

@ -0,0 +1,15 @@
1.0.0: First version of Developer Tools.
1.1.0: Edit plugins with the code editor.
1.1.1: Translate some English texts.
1.1.2: Fixed the Create file issue.
1.1.3: Added new icon for main navigation.
1.1.4: Show the PHP's configuration.
1.1.5: Minor code improvements and bugfix.
1.1.6: The top menu icon shows again.
1.1.7: Fixed the Create folder issue.
1.1.8: !!! Updated for October 420+.
1.1.9:
- Updated the main navigation icon.
- Added last modified date.
1.2.0: The syntax highlighting works again!
1.2.1: Help links open in a new window.

View File

@ -0,0 +1,709 @@
<?php namespace Indikator\DevTools\Widgets;
use Str;
use Url;
use File;
use Lang;
use Input;
use Config;
use Request;
use Response;
use Validator;
use Cms\Classes\Theme;
use Indikator\DevTools\Classes\Asset;
use Backend\Classes\WidgetBase;
use System\Classes\PluginManager;
use ApplicationException;
use ValidationException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use October\Rain\Filesystem\Definitions as FileDefinitions;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use DirectoryIterator;
use Exception;
/**
* CMS asset list widget.
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class AssetList extends WidgetBase
{
use \Backend\Traits\SelectableWidget;
protected $searchTerm = false;
protected $theme;
/**
* @var string Message to display when there are no records in the list.
*/
public $noRecordsMessage = 'cms::lang.asset.no_list_records';
/**
* @var string Message to display when the Delete button is clicked.
*/
public $deleteConfirmation = 'cms::lang.asset.delete_confirm';
/**
* @var array Valid asset file extensions
*/
protected $assetExtensions;
public function __construct($controller, $alias)
{
$this->alias = $alias;
$this->theme = Theme::getEditTheme();
$this->selectionInputName = 'file';
$this->assetExtensions = FileDefinitions::get('assetExtensions');
parent::__construct($controller, []);
$this->bindToController();
$this->checkUploadPostback();
}
/**
* @inheritDoc
*/
protected function loadAssets()
{
$this->addCss('/modules/cms/widgets/assetlist/assets/css/assetlist.css');
$this->addJs('/modules/cms/widgets/assetlist/assets/js/assetlist.js');
}
/**
* Renders the widget.
* @return string
*/
public function render()
{
return $this->makePartial('body', [
'data' => $this->getData()
]);
}
//
// Event handlers
//
public function onOpenDirectory()
{
$path = Input::get('path');
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$delay = Input::get('delay');
if ($delay) {
usleep(1000000 * $delay);
}
$this->putSession('currentPath', $path);
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onRefresh()
{
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onUpdate()
{
$this->extendSelection();
return $this->onRefresh();
}
public function onDeleteFiles()
{
$fileList = Request::input('file');
$error = null;
$deleted = [];
try {
$assetsPath = $this->getAssetsPath();
foreach ($fileList as $path => $selected) {
if ($selected) {
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$fullPath = $assetsPath.'/'.$path;
if (File::exists($fullPath)) {
if (!File::isDirectory($fullPath)) {
if (!@File::delete($fullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_file',
['name' => $path]
));
}
}
else {
$empty = File::isDirectoryEmpty($fullPath);
if ($empty === false) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_dir_not_empty',
['name' => $path]
));
}
if (!@rmdir($fullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_dir',
['name' => $path]
));
}
}
$deleted[] = $path;
$this->removeSelection($path);
}
}
}
}
catch (Exception $ex) {
$error = $ex->getMessage();
}
return [
'deleted' => $deleted,
'error' => $error
];
}
public function onLoadRenamePopup()
{
$path = Input::get('renamePath');
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$this->vars['originalPath'] = $path;
$this->vars['name'] = basename($path);
return $this->makePartial('rename_form');
}
public function onApplyName()
{
$newName = trim(Input::get('name'));
if (!strlen($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty'));
}
if (!$this->validatePath($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
if (!$this->validateName($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name'));
}
$originalPath = Input::get('originalPath');
if (!$this->validatePath($originalPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$originalFullPath = $this->getFullPath($originalPath);
if (!file_exists($originalFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.original_not_found'));
}
if (!is_dir($originalFullPath) && !$this->validateFileType($newName)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.type_not_allowed',
['allowed_types' => implode(', ', $this->assetExtensions)]
));
}
$newFullPath = $this->getFullPath(dirname($originalPath).'/'.$newName);
if (file_exists($newFullPath) && $newFullPath !== $originalFullPath) {
throw new ApplicationException(Lang::get('cms::lang.asset.already_exists'));
}
if (!@rename($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.error_renaming'));
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onLoadNewDirPopup()
{
return $this->makePartial('new_dir_form');
}
public function onNewDirectory()
{
$newName = trim(Input::get('name'));
if (!strlen($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty'));
}
if (!$this->validatePath($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
if (!$this->validateName($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name'));
}
$newFullPath = $this->getCurrentPath().'/'.$newName;
if (file_exists($newFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.already_exists'));
}
if (!File::makeDirectory($newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $newName]
));
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onLoadMovePopup()
{
$fileList = Request::input('file');
$directories = [];
$selectedList = array_filter($fileList, function ($value) {
return $value == 1;
});
$this->listDestinationDirectories($directories, $selectedList);
$this->vars['directories'] = $directories;
$this->vars['selectedList'] = base64_encode(json_encode(array_keys($selectedList)));
return $this->makePartial('move_form');
}
public function onMove()
{
$selectedList = Input::get('selectedList');
if (!strlen($selectedList)) {
throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found'));
}
$destinationDir = Input::get('dest');
if (!strlen($destinationDir)) {
throw new ApplicationException(Lang::get('cms::lang.asset.select_destination_dir'));
}
$destinationFullPath = $this->getFullPath($destinationDir);
if (!file_exists($destinationFullPath) || !is_dir($destinationFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.destination_not_found'));
}
$list = @json_decode(@base64_decode($selectedList));
if ($list === false) {
throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found'));
}
foreach ($list as $path) {
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$basename = basename($path);
$originalFullPath = $this->getFullPath($path);
$newFullPath = rtrim($destinationFullPath, '/').'/'.$basename;
$safeDir = $this->getAssetsPath();
if ($originalFullPath == $newFullPath) {
continue;
}
if (is_file($originalFullPath)) {
if (!@File::move($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_moving_file',
['file' => $basename]
));
}
}
elseif (is_dir($originalFullPath)) {
if (!@File::copyDirectory($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_moving_directory',
['dir' => $basename]
));
}
if (strpos($originalFullPath, '../') !== false) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
if (strpos($originalFullPath, $safeDir) !== 0) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
if (!@File::deleteDirectory($originalFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
}
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onSearch()
{
$this->setSearchTerm(Input::get('search'));
$this->extendSelection();
return $this->onRefresh();
}
/*
* Methods for the internal use
*/
protected function getData()
{
$assetsPath = $this->getAssetsPath();
if (!file_exists($assetsPath) || !is_dir($assetsPath)) {
if (!File::makeDirectory($assetsPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $assetsPath]
));
}
}
$searchTerm = Str::lower($this->getSearchTerm());
if (!strlen($searchTerm)) {
$currentPath = $this->getCurrentPath();
return $this->getDirectoryContents(
new DirectoryIterator($currentPath)
);
}
return $this->findFiles();
}
protected function getAssetsPath()
{
return base_path().'/plugins';
}
protected function getThemeFileUrl($path)
{
return Url::to('plugins'.$path);
}
public function getCurrentRelativePath()
{
$path = $this->getSession('currentPath', '/');
if (!$this->validatePath($path)) {
return null;
}
if ($path == '.') {
return null;
}
return ltrim($path, '/');
}
protected function getCurrentPath()
{
$assetsPath = $this->getAssetsPath();
$path = $assetsPath.'/'.$this->getCurrentRelativePath();
if (!is_dir($path)) {
return $assetsPath;
}
return $path;
}
protected function getRelativePath($path)
{
$prefix = $this->getAssetsPath();
if (substr($path, 0, strlen($prefix)) == $prefix) {
$path = substr($path, strlen($prefix));
}
return $path;
}
protected function getFullPath($path)
{
return $this->getAssetsPath().'/'.ltrim($path, '/');
}
protected function validatePath($path)
{
if (!preg_match('/^[0-9a-z\.\s_\-\/]+$/i', $path)) {
return false;
}
if (strpos($path, '..') !== false || strpos($path, './') !== false) {
return false;
}
return true;
}
protected function validateName($name)
{
if (!preg_match('/^[0-9a-z\.\s_\-]+$/i', $name)) {
return false;
}
if (strpos($name, '..') !== false) {
return false;
}
return true;
}
protected function getDirectoryContents($dir)
{
$editableAssetTypes = Asset::getEditableExtensions();
$result = $files = [];
foreach ($dir as $node) {
if (substr($node->getFileName(), 0, 1) == '.') {
continue;
}
if ($node->isDir() && !$node->isDot()) {
$result[$node->getFilename()] = (object)[
'type' => 'directory',
'path' => File::normalizePath($this->getRelativePath($node->getPathname())),
'name' => $node->getFilename(),
'editable' => false
];
}
elseif ($node->isFile()) {
$files[] = (object)[
'type' => 'file',
'path' => File::normalizePath($this->getRelativePath($node->getPathname())),
'name' => $node->getFilename(),
'editable' => in_array(strtolower($node->getExtension()), $editableAssetTypes)
];
}
}
foreach ($files as $file) {
$result[] = $file;
}
return $result;
}
protected function listDestinationDirectories(&$result, $excludeList, $startDir = null, $level = 0)
{
if ($startDir === null) {
$startDir = $this->getAssetsPath();
$result['/'] = 'assets';
$level = 1;
}
$dirs = new DirectoryIterator($startDir);
foreach ($dirs as $node) {
if (substr($node->getFileName(), 0, 1) == '.') {
continue;
}
if ($node->isDir() && !$node->isDot()) {
$fullPath = $node->getPathname();
$relativePath = $this->getRelativePath($fullPath);
if (array_key_exists($relativePath, $excludeList)) {
continue;
}
$result[$relativePath] = str_repeat('&nbsp;', $level * 4).$node->getFilename();
$this->listDestinationDirectories($result, $excludeList, $fullPath, $level+1);
}
}
}
protected function getSearchTerm()
{
return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
}
protected function isSearchMode()
{
return strlen($this->getSearchTerm());
}
protected function getUpPath()
{
$path = $this->getCurrentRelativePath();
if (!strlen(rtrim(ltrim($path, '/'), '/'))) {
return null;
}
return dirname($path);
}
/**
* Check for valid asset file extension
* @param string
* @return bool
*/
protected function validateFileType($name)
{
$extension = strtolower(File::extension($name));
if (!in_array($extension, $this->assetExtensions)) {
return false;
}
return true;
}
/**
* Checks the current request to see if it is a postback containing a file upload
* for this particular widget.
*/
protected function checkUploadPostback()
{
$fileName = null;
try {
$uploadedFile = Input::file('file_data');
if (!is_object($uploadedFile)) {
return;
}
$fileName = $uploadedFile->getClientOriginalName();
/*
* Check valid upload
*/
if (!$uploadedFile->isValid()) {
throw new ApplicationException(Lang::get('cms::lang.asset.file_not_valid'));
}
/*
* Check file size
*/
$maxSize = UploadedFile::getMaxFilesize();
if ($uploadedFile->getSize() > $maxSize) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.too_large',
['max_size' => File::sizeToString($maxSize)]
));
}
/*
* Check for valid file extensions
*/
if (!$this->validateFileType($fileName)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.type_not_allowed',
['allowed_types' => implode(', ', $this->assetExtensions)]
));
}
/*
* Accept the uploaded file
*/
$uploadedFile->move($this->getCurrentPath(), $uploadedFile->getClientOriginalName());
die('success');
}
catch (Exception $ex) {
$message = $fileName !== null
? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $fileName, 'error' => $ex->getMessage()])
: $ex->getMessage();
die($message);
}
}
protected function setSearchTerm($term)
{
$this->searchTerm = trim($term);
$this->putSession('search', $this->searchTerm);
}
protected function findFiles()
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->getAssetsPath(), RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD
);
$editableAssetTypes = Asset::getEditableExtensions();
$searchTerm = Str::lower($this->getSearchTerm());
$words = explode(' ', $searchTerm);
$result = [];
foreach ($iterator as $item) {
if (!$item->isDir()) {
if (substr($item->getFileName(), 0, 1) == '.') {
continue;
}
$path = $this->getRelativePath($item->getPathname());
if ($this->pathMatchesSearch($words, $path)) {
$result[] = (object)[
'type' => 'file',
'path' => File::normalizePath($path),
'name' => $item->getFilename(),
'editable' => in_array(strtolower($item->getExtension()), $editableAssetTypes)
];
}
}
}
return $result;
}
protected function pathMatchesSearch(&$words, $path)
{
foreach ($words as $word) {
$word = trim($word);
if (!strlen($word)) {
continue;
}
if (!Str::contains(Str::lower($path), $word)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,8 @@
<?= $this->makePartial('toolbar') ?>
<div class="layout-row" id="asset-list-container" data-alias="<?= $this->alias ?>">
<div class="layout-cell">
<div class="layout-relative">
<?= $this->makePartial('files', ['data'=>$data]) ?>
</div>
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="layout-absolute">
<div class="control-scrollbar" data-control="scrollbar">
<div class="control-assetlist" data-control="assetlist" id="<?= $this->getId('asset-list') ?>">
<?= $this->makePartial('items', ['items'=>$data]) ?>
</div>
</div>
</div>

View File

@ -0,0 +1,57 @@
<?php
$searchMode = $this->isSearchMode();
if (($upPath = $this->getUpPath()) !== null && !$searchMode):
?>
<p class="parent">
<a href="<?= $upPath ?>" data-path="<?= $upPath ?>" class="link"><?= $this->getCurrentRelativePath() ?></a>
</p>
<?php endif ?>
<div class="list-container animate">
<?php if ($items): ?>
<ul class="list">
<?php foreach ($items as $item):
$dataId = 'asset-'.ltrim($item->path, '/');
?>
<li class="<?= $item->type ?>" <?php if ($item->editable): ?>data-editable<?php endif ?> data-item-path="<?= e(ltrim($item->path, '/')) ?>" data-item-type="asset" data-id="<?= e($dataId) ?>">
<a class="link" target="_blank" data-path="<?= $item->path ?>" href="<?= $this->getThemeFileUrl($item->path) ?>">
<?= e($item->name) ?>
<?php if ($searchMode): ?>
<span class="description">
<?= e(dirname($item->path)) ?>
</span>
<?php endif ?>
</a>
<div class="controls">
<a
href="javascript:;"
class="control icon btn-primary oc-icon-terminal"
title="<?= e(trans('cms::lang.asset.rename')) ?>"
data-control="popup"
data-request-data="renamePath: '<?= e($item->path) ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><?= e(trans('cms::lang.asset.rename')) ?></a>
</div>
<input type="hidden" name="file[<?= e($item->path) ?>]" value="0">
<div class="checkbox custom-checkbox nolabel">
<?php $cbId = 'cb'.md5($item->path) ?>
<input
id="<?= $cbId ?>"
type="checkbox"
name="file[<?= e($item->path) ?>]"
<?= $this->isItemSelected($item->path) ? 'checked' : null ?>
data-request="<?= $this->getEventHandler('onSelect') ?>"
value="1">
<label for="<?= $cbId ?>"><?= e(trans('cms::lang.asset.select')) ?></label>
</div>
</li>
<?php endforeach ?>
</ul>
<?php else: ?>
<p class="no-data"><?= e(trans($this->noRecordsMessage)) ?></p>
<?php endif ?>
</div>

View File

@ -0,0 +1,40 @@
<?= Form::open([
'data-request'=>$this->getEventHandler('onMove'),
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
'data-stripe-load-indicator'=>1,
'id'=>'asset-move-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.move_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.move_destination')) ?></label>
<select
class="form-control custom-select"
name="dest"
data-placeholder="<?= e(trans('cms::lang.asset.move_please_select')) ?>">
<option></option>
<?php foreach ($directories as $path=>$directory):?>
<option value="<?= e($path) ?>"><?= e($directory) ?></option>
<?php endforeach ?>
</select>
</div>
<input type="hidden" name="selectedList" value="<?= e($selectedList) ?>">
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('cms::lang.asset.move_button')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,45 @@
<?= Form::open([
'data-request'=>$this->getEventHandler('onNewDirectory'),
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
'data-stripe-load-indicator'=>1,
'id'=>'asset-new-dir-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.directory_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.directory_name')) ?></label>
<input
type="text"
name="name"
value=""
class="form-control"
autocomplete="off">
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.create')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function() {
$('#asset-new-dir-popup-form input.form-control').focus()
}, 310
)
</script>
<?= Form::close() ?>

View File

@ -0,0 +1,46 @@
<?= Form::ajax($this->getEventHandler('onApplyName'), [
'success' => "\$el.trigger('close.oc.popup');",
'data-stripe-load-indicator' => 1,
'id' => 'asset-rename-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.rename_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.rename_new_name')) ?></label>
<input
type="text"
name="name"
value="<?= e($name) ?>"
class="form-control"
autocomplete="off">
</div>
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>">
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function() {
$('#asset-rename-popup-form input.form-control').focus()
}, 310
)
</script>
<?= Form::close() ?>

View File

@ -0,0 +1,77 @@
<div class="layout-row min-size">
<div class="control-toolbar toolbar-padded">
<!-- Control Panel -->
<div class="toolbar-item" data-calculate-width>
<div class="btn-group">
<div class="dropdown last">
<button type="button" class="btn btn-default oc-icon-plus"
data-control="create-asset"
data-toggle="dropdown"
><?= e(trans('cms::lang.sidebar.add')) ?></button>
<ul class="dropdown-menu offset-left" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_add_title')) ?>">
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="create-template" class="oc-icon-file-text-o"><?= e(trans('cms::lang.asset.create_file')) ?></a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="upload-assets" class="oc-icon-upload"><?= e(trans('cms::lang.asset.upload_files')) ?></a></li>
<li role="presentation" class="divider"></li>
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
class="oc-icon-folder-o"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadNewDirPopup') ?>"
><?= e(trans('cms::lang.asset.create_directory')) ?></a></li>
</ul>
</div>
<div class="dropdown hide"
id="<?= $this->getId('tools-button') ?>"
data-trigger-action="show"
data-trigger="<?= '#'.$this->getId('asset-list') ?> input[type=checkbox]"
data-trigger-condition="checked">
<button type="button" class="btn btn-default empty oc-icon-wrench last"
data-toggle="dropdown"
data-control="asset-tools"
></button>
<ul class="dropdown-menu" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_operation_title')) ?>">
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
data-control="delete-asset"
data-confirmation="<?= e(trans($this->deleteConfirmation)) ?>"
class="oc-icon-trash-o"
><?= e(trans('cms::lang.asset.delete')) ?></a></li>
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
class="oc-icon-angle-double-right"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadMovePopup') ?>"
><?= e(trans('cms::lang.asset.move')) ?></a></li>
</ul>
</div>
</div>
</div>
<!-- Asset Search -->
<div class="relative toolbar-item loading-indicator-container size-input-text">
<input
type="text"
name="search"
value="<?= e($this->getSearchTerm()) ?>"
class="form-control icon search" autocomplete="off"
placeholder="<?= e(trans('cms::lang.sidebar.search')) ?>"
data-track-input
data-load-indicator
data-load-indicator-opaque
data-request-success="$('<?= '#'.$this->getId('tools-button') ?>').trigger('oc.triggerOn.update')"
data-request="<?= $this->getEventHandler('onSearch') ?>"
>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
<?php namespace October\Demo;
/**
* The plugin.php file (called the plugin initialization script) defines the plugin information class.
*/
use System\Classes\PluginBase;
class Plugin extends PluginBase
{
public function pluginDetails()
{
return [
'name' => 'October Demo',
'description' => 'Provides features used by the provided demonstration theme.',
'author' => 'Alexey Bobkov, Samuel Georges',
'icon' => 'icon-leaf'
];
}
public function registerComponents()
{
return [
'\October\Demo\Components\Todo' => 'demoTodo'
];
}
}

View File

@ -0,0 +1,45 @@
<?php namespace October\Demo\Components;
use Cms\Classes\ComponentBase;
use ApplicationException;
class Todo extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Todo List',
'description' => 'Implements a simple to-do list.'
];
}
public function defineProperties()
{
return [
'max' => [
'description' => 'The most amount of todo items allowed',
'title' => 'Max items',
'default' => 10,
'type' => 'string',
'validationPattern' => '^[0-9]+$',
'validationMessage' => 'The Max Items value is required and should be integer.'
]
];
}
public function onAddItem()
{
$items = post('items', []);
if (count($items) >= $this->property('max')) {
throw new ApplicationException(sprintf('Sorry only %s items are allowed.', $this->property('max')));
}
if (($newItem = post('newItem')) != '') {
$items[] = $newItem;
}
$this->page['items'] = $items;
}
}

View File

@ -0,0 +1,22 @@
<form
role="form"
data-request="{{ __SELF__ }}::onAddItem"
data-request-update="'{{ __SELF__ }}::list': '#result'"
data-request-success="$('#input-item').val('')"
data-request-flash>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">To Do List</h3>
</div>
<div class="panel-body">
<div class="input-group">
<input type="text" id="input-item" class="form-control" value="" name="newItem" placeholder="What needs to be done?">
<span class="input-group-btn">
<button type="submit" class="btn btn btn-primary" data-attach-loading>Add</button>
</span>
</div>
</div>
<ul class="list-group" id="result">
</ul>
</div>
</form>

View File

@ -0,0 +1,12 @@
{% for item in items %}
<li class="list-group-item">
<input type="hidden" name="items[]" value="{{ item }}" />
{{ item }}
<button type="button"
class="close pull-right"
aria-hidden="true"
onclick="$(this).closest('li').remove()">&times;</button>
</li>
{% endfor %}

View File

@ -0,0 +1,34 @@
{
"name": "october/demo-plugin",
"type": "october-plugin",
"description": "Demo OctoberCMS plugin",
"keywords": ["october", "cms", "demo", "plugin"],
"license": "MIT",
"authors": [
{
"name": "Alexey Bobkov",
"email": "aleksey.bobkov@gmail.com",
"role": "Co-founder"
},
{
"name": "Samuel Georges",
"email": "daftspunky@gmail.com",
"role": "Co-founder"
}
],
"require": {
"php": ">=5.4",
"composer/installers": "~1.0"
},
"autoload": {
"psr-4": {
"October\\Demo\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}

View File

@ -0,0 +1 @@
1.0.1: First version of Demo

View File

@ -122,6 +122,16 @@ class Post extends Model
*/
public function filterFields($fields, $context = null)
{
if(isset($fields->locale)){
if($this->locale == 'en')
{
$fields->tags_en->hidden = false;
$fields->tags_ru->hidden = true;
}else{
$fields->tags_en->hidden = true;
$fields->tags_ru->hidden = false;
}
}
if (!isset($fields->published, $fields->published_at)) {
return;
}

View File

@ -1,5 +1,6 @@
<?php namespace Tps\Reklama;
use Config;
use System\Classes\PluginBase;
class Plugin extends PluginBase
@ -31,4 +32,20 @@ class Plugin extends PluginBase
]
];
}
public function registerMarkupTags() {
return [
'filters' => [
// A global function, i.e str_plural()
'media_cdn' => [$this, 'absoluteMediaUrl'],
],
];
}
public function absoluteMediaUrl($file) {
$original = \System\Classes\MediaLibrary::url($file);
// modify here and return
return Config::get('cms.cdn').$original;
}
}

View File

@ -13,7 +13,7 @@
data-request-data = '[{id:{{reklama.id}}},{url:"{{reklama.url}}"}]'
{% endif %}
>
<img src="{{reklama.media|media}}" alt="{{reklama.title}}">
<img src="{{reklama.media|media_cdn}}" alt="{{reklama.title}}">
</a>
{% endfor %}

View File

@ -5,5 +5,5 @@
data-request-data = '[{id:{{reklama.id}}},{url:"{{reklama.url}}"}]'
{% endif %}
>
<img src="{{reklama.media|media}}" alt="{{reklama.title}}">
<img src="{{reklama.media|media_cdn}}" alt="{{reklama.title}}">
</a>

View File

@ -1,12 +1,56 @@
<div class="sideSlider">
{% if group and group.adds %}
<div class="advertisiment_{{__SELF__}}">
{% for reklama in group.adds %}
<a class="sideSlider__item"
<a class="advertisiment__item"
{% if reklama.enable_stats and reklama.url !="#" %}
data-request="{{__SELF__}}::onRedirect"
data-request-data = '[{id:{{reklama.id}}},{url:"{{reklama.url}}"}]'
{% endif %}
>
<img src="{{reklama.media|media}}" alt="{{reklama.title}}">
<img src="{{reklama.media|media_cdn}}" alt="{{reklama.title}}">
</a>
{% endfor %}
</div>
<script>
// advertisement start
$(function () {
var $slideshow = $(".advertisiment_{{__SELF__}}");
var ImagePauses = {{group.adds.pluck('display')}};
// Init
$slideshow.slick({
dots: false,
arrows: false,
infinite: true,
initialSlide: 0,
autoplay: true,
autoplaySpeed: ImagePauses[0] * 1000,
fade: true,
cssEase: "linear",
adaptiveHeight: true,
});
// Sliding settings
$slideshow.on("afterChange", function (event, slick, currentSlide) {
// Console log, can be removed
console.log(
"Current slide: " +
currentSlide +
". Setting speed to: " +
ImagePauses[currentSlide]
);
// Update autoplay speed according to slide index
$slideshow.slick(
"slickSetOption",
"autoplaySpeed",
ImagePauses[currentSlide] * 1000,
true
);
});
});
// advertisement end
</script>
{% endif %}

View File

@ -251,4 +251,6 @@
'AhmadFatoni\\ApiGenerator\\Controllers\\ApiGeneratorController' => 'plugins/ahmadfatoni/apigenerator/controllers/ApiGeneratorController.php',
'Zen\\Robots\\Controllers\\Generate' => 'plugins/zen/robots/controllers/Generate.php',
'Tps\\Reklama\\Widgets\\Stats' => 'plugins/tps/reklama/widgets/Stats.php',
'indikator\\devtools\\Plugin' => 'plugins/indikator/devtools/Plugin.php',
'Indikator\\DevTools\\Models\\Settings' => 'plugins/indikator/devtools/models/Settings.php',
);

View File

@ -121,41 +121,4 @@ if (
);
// lazyload end
// =============================================
// advertisement start
$(function () {
var $slideshow = $(".advertisiment");
var ImagePauses = [6000, 2000, 3000, 10000];
// Init
$slideshow.slick({
dots: false,
arrows: false,
infinite: true,
initialSlide: 0,
autoplay: true,
autoplaySpeed: ImagePauses[0],
fade: true,
cssEase: "linear",
adaptiveHeight: true,
});
// Sliding settings
$slideshow.on("afterChange", function (event, slick, currentSlide) {
// Console log, can be removed
console.log(
"Current slide: " +
currentSlide +
". Setting speed to: " +
ImagePauses[currentSlide]
);
// Update autoplay speed according to slide index
$slideshow.slick(
"slickSetOption",
"autoplaySpeed",
ImagePauses[currentSlide],
true
);
});
});
// advertisement end

View File

@ -1,25 +1,26 @@
items:
-
title: Главная
nesting: null
type: url
url: /
code: ''
reference: null
cmsPage: null
replace: null
viewBag:
locale:
en:
title: Home
url: ''
isHidden: '1'
isHidden: '0'
cssClass: ''
isExternal: '0'
-
title: Рубрики
nesting: null
type: header
url: null
code: ''
reference: null
cmsPage: null
replace: null
viewBag:
locale:
en:
@ -31,8 +32,11 @@ items:
items:
-
title: 'New menu item'
nesting: null
type: all-blog-categories
url: null
code: ''
reference: null
cmsPage: category
replace: 1
viewBag:

View File

@ -18,10 +18,20 @@ code = "media"
type = "carousel"
random = 0
[adverts adv_bank]
code = "bank"
type = "slider"
random = 0
[adverts samsung]
code = "samsung"
type = "single"
random = 0
[adverts adv_bottom]
code = "bottom"
type = "slider"
random = 0
==
{% partial 'slider' %}
@ -31,20 +41,6 @@ random = 0
<section class="banner">
<div class="auto__container">
<div class="banner__inner">
<div class="advertisement">
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
</div>
{% component 'samsung' %}
</div>
</div>
@ -63,21 +59,7 @@ random = 0
<div class="main__inner">
<div class="main__content">
<div class="main__banner">
<div class="advertisement">
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
</div>
<!--{% component 'reklama' %}-->
{% component 'adv_bank' %}
</div>
<div class="main__body">
<div class="main__body-row">
@ -86,20 +68,7 @@ random = 0
</div>
</div>
<div class="main__banner">
<div class="advertisement">
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
</div>
{% component 'adv_bottom' %}
</div>
<div class="main__body">
<div class="main__body-row">
@ -108,20 +77,7 @@ random = 0
</div>
</div>
<div class="main__banner">
<div class="advertisement">
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
</div>
<!-- {% component 'slider' %}-->
</div>
<div class="main__body">
<div class="main__body-row">
@ -130,20 +86,7 @@ random = 0
</div>
</div>
<div class="main__banner">
<div class="advertisement">
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
<a href="#" class="advertisiment__item">
<img src="{{'assets/images/example.svg'|theme}}" alt="">
</a>
</div>
<!-- {% component 'adv_bottom' %}-->
</div>
{% partial 'index/partner_news'%}
</div>
@ -183,4 +126,4 @@ random = 0
{% put scripts %}
<script src="{{['assets/slick/slick.min.js','assets/js/lazy.js','assets/js/main.js']|theme}}"></script>
{% endput %}
{% endput %}

View File

@ -48,7 +48,7 @@ slug = "{{ :slug }}"
</h1>
{% if post.featured_image %}
<picture class="news__image">
<img src="{{post.featured_image|media|modify({filter:'post'})}}" alt="{{post.title}}">
<img src="{{post.featured_image|media_cdn|modify({filter:'post'})}}" alt="{{post.title}}">
</picture>
{% endif %}
</header>
@ -72,5 +72,29 @@ slug = "{{ :slug }}"
update: { view: '@#view' },
})
var re = /\[video poster=\"(.+?)\".+?mp4=\"(.+?)\"/g;
// re = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g
var ptags = document.querySelectorAll('p');
ptags.forEach(s =>{
var m;
m = re.exec(s.innerText);
if (m) {
var video =document.createElement('video');
video.setAttribute('src',m[2]);
video.setAttribute('controls',"")
video.setAttribute('width',"100%")
video.setAttribute('type',"video/mp4")
video.setAttribute('poster',m[1])
s.parentNode.replaceChild(video,s);
console.log(m);
console.log(m[1]);
}
else{
}
});
</script>
{% endput %}
{% endput %}

View File

@ -8,7 +8,7 @@
{{'page.more'|_}}
</a>
</div>
<a href="{{posts.first.featured_image|media| modify({ filter: 'thumb-medium' })}}" class="primary progressive replace">
<a href="{{posts.first.featured_image|media_cdn| modify({ filter: 'thumb-medium' })}}" class="primary progressive replace">
<img class="preview" src="{{'assets/images/news/1.jpg'|theme}}" alt="{{posts.first.title}}">
</a>
<div class="main__body-card">
@ -32,7 +32,7 @@
</a>
</div>
{% partial 'index/category_post_item' post = posts.1 %}
{% partial 'index/category_post_item' post = posts.first %}
{% partial 'index/category_post_item' post = posts.last %}
</div>
</div>

View File

@ -13,7 +13,7 @@
<div class="event__body">
<div class="event__body-row">
<div class="event__body-image">
<a href="{{firstPost.featured_image|media| modify({ filter: 'thumb-large' })}}" class="primary progressive replace">
<a href="{{firstPost.featured_image|media_cdn| modify({ filter: 'thumb-large' })}}" class="primary progressive replace">
<img class="preview" src="{{'assets/images/news/1.jpg'|theme}}" alt="{{firstPost.title}}">
</a>
<div class="event__body-card">

View File

@ -1,8 +1,8 @@
<div class="heading__row">
<a href="{{post.featured_image|media}}" class="heading__image primary progressive replace">
<a href="{{post.featured_image|media_cdn}}" class="heading__image primary progressive replace">
<!-- <picture>-->
<!-- <source media="min-width:650px" srcset="{{post.featured_image|media}}">-->
<!-- <img class="preview" src="{{post.featured_image|media}}" alt="">-->
<!-- <source media="min-width:650px" srcset="{{post.featured_image|media_cdn}}">-->
<!-- <img class="preview" src="{{post.featured_image|media_cdn}}" alt="">-->
<!-- </picture>-->
<img class="preview" src="{{'assets/images/news/1.jpg'|theme}}" alt="">
</a>

View File

@ -15,7 +15,7 @@ postPage = "post"
<div class="slider__inner">
{% for post in posts %}
<div class="slider__item">
<img src="{{post.featured_image|media| modify({ filter: 'slider' })}}" alt="{{post.title}}">
<img src="{{post.featured_image|media_cdn| modify({ filter: 'slider' })}}" alt="{{post.title}}">
{% if post.categories.count()>0%}
<div class="slider__item-category">
{{post.categories.implode('name', ', ')}}

View File

@ -6,14 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php',
'JSMin' => $vendorDir . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'JSMinException' => $vendorDir . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
);

View File

@ -8,9 +8,7 @@ $baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',

View File

@ -12,6 +12,5 @@ return array(
'Parsedown' => array($vendorDir . '/erusev/parsedown'),
'Less' => array($vendorDir . '/october/rain/src/Parse/Assetic/Less/lib'),
'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/lib'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib'),
'Assetic' => array($vendorDir . '/kriswallsmith/assetic/src'),
);

View File

@ -7,19 +7,17 @@ $baseDir = dirname($vendorDir);
return array(
'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),
'Wikimedia\\Composer\\' => array($vendorDir . '/wikimedia/composer-merge-plugin/src'),
'Twig\\' => array($vendorDir . '/twig/twig/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
'System\\' => array($baseDir . '/modules/system'),
'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
@ -39,8 +37,8 @@ return array(
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'October\\Rain\\' => array($vendorDir . '/october/rain/src'),
'October\\Demo\\' => array($baseDir . '/plugins/october/demo'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'League\\Csv\\' => array($vendorDir . '/league/csv/src'),
'Leafo\\ScssPhp\\' => array($vendorDir . '/leafo/scssphp/src'),
@ -51,8 +49,10 @@ return array(
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'),
'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'),
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib/Doctrine/Common'),

View File

@ -9,9 +9,7 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
@ -32,10 +30,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
'XdgBaseDir\\' => 11,
),
'W' =>
array (
'Wikimedia\\Composer\\' => 19,
),
'T' =>
array (
'Twig\\' => 5,
@ -44,15 +38,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'S' =>
array (
'System\\' => 7,
'Symfony\\Polyfill\\Util\\' => 22,
'Symfony\\Polyfill\\Php72\\' => 23,
'Symfony\\Polyfill\\Php70\\' => 23,
'Symfony\\Polyfill\\Php56\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Symfony\\Polyfill\\Iconv\\' => 23,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Translation\\' => 30,
@ -81,7 +74,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'O' =>
array (
'October\\Rain\\' => 13,
'October\\Demo\\' => 13,
),
'M' =>
array (
@ -89,6 +81,7 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
),
'L' =>
array (
'League\\MimeTypeDetection\\' => 25,
'League\\Flysystem\\' => 17,
'League\\Csv\\' => 11,
'Leafo\\ScssPhp\\' => 14,
@ -111,8 +104,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'D' =>
array (
'Dotenv\\' => 7,
'Doctrine\\Inflector\\' => 19,
'Doctrine\\Common\\Lexer\\' => 22,
'Doctrine\\Common\\Inflector\\' => 26,
'Doctrine\\Common\\Collections\\' => 28,
'Doctrine\\Common\\Cache\\' => 22,
'Doctrine\\Common\\Annotations\\' => 28,
'Doctrine\\Common\\' => 16,
@ -134,10 +129,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src',
),
'Wikimedia\\Composer\\' =>
array (
0 => __DIR__ . '/..' . '/wikimedia/composer-merge-plugin/src',
),
'Twig\\' =>
array (
0 => __DIR__ . '/..' . '/twig/twig/src',
@ -150,22 +141,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/../..' . '/modules/system',
),
'Symfony\\Polyfill\\Util\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-util',
),
'Symfony\\Polyfill\\Php72\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
),
'Symfony\\Polyfill\\Php70\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php70',
),
'Symfony\\Polyfill\\Php56\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php56',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
@ -186,6 +165,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
@ -262,14 +249,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/october/rain/src',
),
'October\\Demo\\' =>
array (
0 => __DIR__ . '/../..' . '/plugins/october/demo',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'League\\MimeTypeDetection\\' =>
array (
0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
),
'League\\Flysystem\\' =>
array (
0 => __DIR__ . '/..' . '/league/flysystem/src',
@ -310,6 +297,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src',
),
'Doctrine\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector',
),
'Doctrine\\Common\\Lexer\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
@ -318,6 +309,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Common/Inflector',
),
'Doctrine\\Common\\Collections\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/collections/lib/Doctrine/Common/Collections',
),
'Doctrine\\Common\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache',
@ -391,10 +386,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/doctrine/dbal/lib',
),
'Doctrine\\Common\\Collections\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/collections/lib',
),
),
'A' =>
array (
@ -406,16 +397,9 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
);
public static $classMap = array (
'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php',
'JSMin' => __DIR__ . '/..' . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'JSMinException' => __DIR__ . '/..' . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
);
public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large Load Diff

View File

@ -57,12 +57,14 @@
"PPI",
"Puppet",
"Porto",
"ProcessWire",
"RadPHP",
"ReIndex",
"Roundcube",
"shopware",
"SilverStripe",
"SMF",
"Starbug",
"SyDES",
"Sylius",
"symfony",
@ -86,10 +88,13 @@
"autoload": {
"psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
},
"autoload-dev": {
"psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" }
},
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-master": "1.0-dev"
"dev-main": "1.x-dev"
}
},
"replace": {
@ -100,13 +105,15 @@
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "1.6.* || 2.0.*@dev",
"composer/semver": "1.0.* || 2.0.*@dev",
"phpunit/phpunit": "^4.8.36",
"sebastian/comparator": "^1.2.4",
"symfony/process": "^2.3"
"composer/composer": "1.6.* || ^2.0",
"composer/semver": "^1 || ^3",
"symfony/phpunit-bridge": "^4.2 || ^5",
"phpstan/phpstan": "^0.12.55",
"symfony/process": "^2.3",
"phpstan/phpstan-phpunit": "^0.12.16"
},
"scripts": {
"test": "phpunit"
"test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
"phpstan": "vendor/bin/phpstan analyse"
}
}

View File

@ -74,8 +74,8 @@ abstract class BaseInstaller
/**
* For an installer to override to modify the vars per installer.
*
* @param array $vars
* @return array
* @param array<string, string> $vars This will normally receive array{name: string, vendor: string, type: string}
* @return array<string, string>
*/
public function inflectPackageVars($vars)
{
@ -85,7 +85,7 @@ abstract class BaseInstaller
/**
* Gets the installer's locations
*
* @return array
* @return array<string, string> map of package types => install path
*/
public function getLocations()
{
@ -95,8 +95,8 @@ abstract class BaseInstaller
/**
* Replace vars in a path
*
* @param string $path
* @param array $vars
* @param string $path
* @param array<string, string> $vars
* @return string
*/
protected function templatePath($path, array $vars = array())
@ -121,7 +121,7 @@ abstract class BaseInstaller
* @param string $name
* @param string $type
* @param string $vendor = NULL
* @return string
* @return string|false
*/
protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
{

View File

@ -2,6 +2,7 @@
namespace Composer\Installers;
use Composer\DependencyResolver\Pool;
use Composer\Semver\Constraint\Constraint;
class CakePHPInstaller extends BaseInstaller
{
@ -49,14 +50,6 @@ class CakePHPInstaller extends BaseInstaller
*/
protected function matchesCakeVersion($matcher, $version)
{
if (class_exists('Composer\Semver\Constraint\MultiConstraint')) {
$multiClass = 'Composer\Semver\Constraint\MultiConstraint';
$constraintClass = 'Composer\Semver\Constraint\Constraint';
} else {
$multiClass = 'Composer\Package\LinkConstraint\MultiConstraint';
$constraintClass = 'Composer\Package\LinkConstraint\VersionConstraint';
}
$repositoryManager = $this->composer->getRepositoryManager();
if (! $repositoryManager) {
return false;
@ -67,6 +60,6 @@ class CakePHPInstaller extends BaseInstaller
return false;
}
return $repos->findPackage('cakephp/cakephp', new $constraintClass($matcher, $version)) !== null;
return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null;
}
}

View File

@ -12,9 +12,7 @@ class CockpitInstaller extends BaseInstaller
*
* Strip `module-` prefix from package name.
*
* @param array @vars
*
* @return array
* {@inheritDoc}
*/
public function inflectPackageVars($vars)
{

View File

@ -9,6 +9,7 @@ use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
class Installer extends LibraryInstaller
{
@ -87,6 +88,7 @@ class Installer extends LibraryInstaller
'radphp' => 'RadPHPInstaller',
'phifty' => 'PhiftyInstaller',
'porto' => 'PortoInstaller',
'processwire' => 'ProcessWireInstaller',
'redaxo' => 'RedaxoInstaller',
'redaxo5' => 'Redaxo5Installer',
'reindex' => 'ReIndexInstaller',
@ -95,6 +97,7 @@ class Installer extends LibraryInstaller
'sitedirect' => 'SiteDirectInstaller',
'silverstripe' => 'SilverStripeInstaller',
'smf' => 'SMFInstaller',
'starbug' => 'StarbugInstaller',
'sydes' => 'SyDESInstaller',
'sylius' => 'SyliusInstaller',
'symfony1' => 'Symfony1Installer',
@ -160,9 +163,23 @@ class Installer extends LibraryInstaller
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{
parent::uninstall($repo, $package);
$installPath = $this->getPackageBasePath($package);
$this->io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
$io = $this->io;
$outputStatus = function () use ($io, $installPath) {
$io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
};
$promise = parent::uninstall($repo, $package);
// Composer v2 might return a promise here
if ($promise instanceof PromiseInterface) {
return $promise->then($outputStatus);
}
// If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
$outputStatus();
return null;
}
/**
@ -184,23 +201,20 @@ class Installer extends LibraryInstaller
/**
* Finds a supported framework type if it exists and returns it
*
* @param string $type
* @return string
* @param string $type
* @return string|false
*/
protected function findFrameworkType($type)
{
$frameworkType = false;
krsort($this->supportedTypes);
foreach ($this->supportedTypes as $key => $val) {
if ($key === substr($type, 0, strlen($key))) {
$frameworkType = substr($type, 0, strlen($key));
break;
return substr($type, 0, strlen($key));
}
}
return $frameworkType;
return false;
}
/**

View File

@ -18,6 +18,7 @@ class MoodleInstaller extends BaseInstaller
'cachestore' => 'cache/stores/{$name}/',
'cachelock' => 'cache/locks/{$name}/',
'calendartype' => 'calendar/type/{$name}/',
'fileconverter' => 'files/converter/{$name}/',
'format' => 'course/format/{$name}/',
'coursereport' => 'course/report/{$name}/',
'customcertelement' => 'mod/customcert/element/{$name}/',

View File

@ -18,7 +18,7 @@ class OxidInstaller extends BaseInstaller
*
* @param PackageInterface $package
* @param string $frameworkType
* @return void
* @return string
*/
public function getInstallPath(PackageInterface $package, $frameworkType = '')
{

View File

@ -13,9 +13,7 @@ class SyDESInstaller extends BaseInstaller
*
* Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present.
*
* @param array @vars
*
* @return array
* {@inerhitDoc}
*/
public function inflectPackageVars($vars)
{

View File

@ -6,7 +6,25 @@ namespace Composer\Installers;
*/
class TaoInstaller extends BaseInstaller
{
const EXTRA_TAO_EXTENSION_NAME = 'tao-extension-name';
protected $locations = array(
'extension' => '{$name}'
);
public function inflectPackageVars($vars)
{
$extra = $this->package->getExtra();
if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) {
$vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME];
return $vars;
}
$vars['name'] = str_replace('extension-', '', $vars['name']);
$vars['name'] = str_replace('-', ' ', $vars['name']);
$vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name'])));
return $vars;
}
}

View File

@ -1,5 +1,52 @@
## Changelog
### 1.6.1
This release fixes an issue in which annotations such as `@foo-bar`
and `@foo-` were incorrectly recognised as valid, and both erroneously
parsed as `@foo`.
Any annotation with `@name-*` format will now silently be ignored,
allowing vendor-specific annotations to be prefixed with the tool
name.
Total issues resolved: **3**
- [165: Update the composer branch alias](https://github.com/doctrine/annotations/pull/165) thanks to @mikeSimonson
- [209: Change Annotation::value typehint to mixed](https://github.com/doctrine/annotations/pull/209) thanks to @malarzm
- [257: Skip parsing annotations containing dashes, such as `@Foo-bar`, or `@Foo-`](https://github.com/doctrine/annotations/pull/257) thanks to @Ocramius
### 1.6.0
This release brings a new endpoint that make sure that you can't shoot yourself in the foot by calling ```registerLoader``` multiple times and a few tests improvements.
Total issues resolved: **7**
- [145: Memory leak in AnnotationRegistry::registerLoader() when called multiple times](https://github.com/doctrine/annotations/issues/145) thanks to @TriAnMan
- [146: Import error on @experimental Annotation](https://github.com/doctrine/annotations/issues/146) thanks to @aturki
- [147: Ignoring @experimental annotation used by Symfony 3.3 CacheAdapter](https://github.com/doctrine/annotations/pull/147) thanks to @aturki
- [151: Remove duplicate code in `DCOM58Test`](https://github.com/doctrine/annotations/pull/151) thanks to @tuanphpvn
- [161: Prevent loading class&#95;exists multiple times](https://github.com/doctrine/annotations/pull/161) thanks to @jrjohnson
- [162: Add registerUniqueLoader to AnnotationRegistry](https://github.com/doctrine/annotations/pull/162) thanks to @jrjohnson
- [163: Use assertDirectoryExists and assertDirectoryNotExists](https://github.com/doctrine/annotations/pull/163) thanks to @carusogabriel
Thanks to everyone involved in this release.
### 1.5.0
This release increments the minimum supported PHP version to 7.1.0.
Also, HHVM official support has been dropped.
Some noticeable performance improvements to annotation autoloading
have been applied, making failed annotation autoloading less heavy
on the filesystem access.
- [133: Add @throws annotation in AnnotationReader#__construct()](https://github.com/doctrine/annotations/issues/133) thanks to @SenseException
- [134: Require PHP 7.1, drop HHVM support](https://github.com/doctrine/annotations/issues/134) thanks to @lcobucci
- [135: Prevent the same loader from being registered twice](https://github.com/doctrine/annotations/issues/135) thanks to @jrjohnson
- [137: #135 optimise multiple class load attempts in AnnotationRegistry](https://github.com/doctrine/annotations/issues/137) thanks to @Ocramius
### 1.4.0

View File

@ -1,16 +1,21 @@
# Doctrine Annotations
[![Build Status](https://travis-ci.org/doctrine/annotations.svg?branch=master)](https://travis-ci.org/doctrine/annotations)
[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://poser.pugx.org/doctrine/annotations/v/stable.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations)
Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)).
## Documentation
See the [doctrine-project website](http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html).
See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html).
## Contributing
When making a pull request, make sure your changes follow the
[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction).
## Changelog

View File

@ -3,7 +3,7 @@
"type": "library",
"description": "Docblock Annotations Parser",
"keywords": ["annotations", "docblock", "parser"],
"homepage": "http://www.doctrine-project.org",
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
@ -13,22 +13,30 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^5.6 || ^7.0",
"php": "^7.1 || ^8.0",
"ext-tokenizer": "*",
"doctrine/lexer": "1.*"
},
"require-dev": {
"doctrine/cache": "1.*",
"phpunit/phpunit": "^5.7"
"doctrine/coding-standard": "^6.0 || ^8.1",
"phpstan/phpstan": "^0.12.20",
"phpunit/phpunit": "^7.5 || ^9.1.5"
},
"config": {
"sort-packages": true
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" }
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" }
},
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
"psr-4": {
"Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations",
"Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations"
},
"files": [
"tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php",
"tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php"
]
}
}

View File

@ -0,0 +1,271 @@
Handling Annotations
====================
There are several different approaches to handling annotations in PHP.
Doctrine Annotations maps docblock annotations to PHP classes. Because
not all docblock annotations are used for metadata purposes a filter is
applied to ignore or skip classes that are not Doctrine annotations.
Take a look at the following code snippet:
.. code-block:: php
namespace MyProject\Entities;
use Doctrine\ORM\Mapping AS ORM;
use Symfony\Component\Validator\Constraints AS Assert;
/**
* @author Benjamin Eberlei
* @ORM\Entity
* @MyProject\Annotations\Foobarable
*/
class User
{
/**
* @ORM\Id @ORM\Column @ORM\GeneratedValue
* @dummy
* @var int
*/
private $id;
/**
* @ORM\Column(type="string")
* @Assert\NotEmpty
* @Assert\Email
* @var string
*/
private $email;
}
In this snippet you can see a variety of different docblock annotations:
- Documentation annotations such as ``@var`` and ``@author``. These
annotations are ignored and never considered for throwing an
exception due to wrongly used annotations.
- Annotations imported through use statements. The statement ``use
Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
available as ``@ORM\ClassName``. Same goes for the import of
``@Assert``.
- The ``@dummy`` annotation. It is not a documentation annotation and
not ignored. For Doctrine Annotations it is not entirely clear how
to handle this annotation. Depending on the configuration an exception
(unknown annotation) will be thrown when parsing this annotation.
- The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
This is transformed directly into the given class name.
How are these annotations loaded? From looking at the code you could
guess that the ORM Mapping, Assert Validation and the fully qualified
annotation can just be loaded using
the defined PHP autoloaders. This is not the case however: For error
handling reasons every check for class existence inside the
``AnnotationReader`` sets the second parameter $autoload
of ``class_exists($name, $autoload)`` to false. To work flawlessly the
``AnnotationReader`` requires silent autoloaders which many autoloaders are
not. Silent autoloading is NOT part of the `PSR-0 specification
<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
for autoloading.
This is why Doctrine Annotations uses its own autoloading mechanism
through a global registry. If you are wondering about the annotation
registry being global, there is no other way to solve the architectural
problems of autoloading annotation classes in a straightforward fashion.
Additionally if you think about PHP autoloading then you recognize it is
a global as well.
To anticipate the configuration section, making the above PHP class work
with Doctrine Annotations requires this setup:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php");
AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src");
AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src");
$reader = new AnnotationReader();
AnnotationReader::addGlobalIgnoredName('dummy');
The second block with the annotation registry calls registers all the
three different annotation namespaces that are used.
Doctrine Annotations saves all its annotations in a single file, that is
why ``AnnotationRegistry#registerFile`` is used in contrast to
``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0
compatible loading mechanism for class to file names.
In the third block, we create the actual ``AnnotationReader`` instance.
Note that we also add ``dummy`` to the global list of ignored
annotations for which we do not throw exceptions. Setting this is
necessary in our example case, otherwise ``@dummy`` would trigger an
exception to be thrown during the parsing of the docblock of
``MyProject\Entities\User#id``.
Setup and Configuration
-----------------------
To use the annotations library is simple, you just need to create a new
``AnnotationReader`` instance:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
This creates a simple annotation reader with no caching other than in
memory (in php arrays). Since parsing docblocks can be expensive you
should cache this process by using a caching reader.
You can use a file caching reader, but please note it is deprecated to
do so:
.. code-block:: php
use Doctrine\Common\Annotations\FileCacheReader;
use Doctrine\Common\Annotations\AnnotationReader;
$reader = new FileCacheReader(
new AnnotationReader(),
"/path/to/cache",
$debug = true
);
If you set the ``debug`` flag to ``true`` the cache reader will check
for changes in the original files, which is very important during
development. If you don't set it to ``true`` you have to delete the
directory to clear the cache. This gives faster performance, however
should only be used in production, because of its inconvenience during
development.
You can also use one of the ``Doctrine\Common\Cache\Cache`` cache
implementations to cache the annotations:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Cache\ApcCache;
$reader = new CachedReader(
new AnnotationReader(),
new ApcCache(),
$debug = true
);
The ``debug`` flag is used here as well to invalidate the cache files
when the PHP class with annotations changed and should be used during
development.
.. warning ::
The ``AnnotationReader`` works and caches under the
assumption that all annotations of a doc-block are processed at
once. That means that annotation classes that do not exist and
aren't loaded and cannot be autoloaded (using the
AnnotationRegistry) would never be visible and not accessible if a
cache is used unless the cache is cleared and the annotations
requested again, this time with all annotations defined.
By default the annotation reader returns a list of annotations with
numeric indexes. If you want your annotations to be indexed by their
class name you can wrap the reader in an ``IndexedReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\IndexedReader;
$reader = new IndexedReader(new AnnotationReader());
.. warning::
You should never wrap the indexed reader inside a cached reader,
only the other way around. This way you can re-use the cache with
indexed or numeric keys, otherwise your code may experience failures
due to caching in a numerical or indexed format.
Registering Annotations
~~~~~~~~~~~~~~~~~~~~~~~
As explained in the introduction, Doctrine Annotations uses its own
autoloading mechanism to determine if a given annotation has a
corresponding PHP class that can be autoloaded. For annotation
autoloading you have to configure the
``Doctrine\Common\Annotations\AnnotationRegistry``. There are three
different mechanisms to configure annotation autoloading:
- Calling ``AnnotationRegistry#registerFile($file)`` to register a file
that contains one or more annotation classes.
- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs =
null)`` to register that the given namespace contains annotations and
that their base directory is located at the given $dirs or in the
include path if ``NULL`` is passed. The given directories should *NOT*
be the directory where classes of the namespace are in, but the base
directory of the root namespace. The AnnotationRegistry uses a
namespace to directory separator approach to resolve the correct path.
- Calling ``AnnotationRegistry#registerLoader($callable)`` to register
an autoloader callback. The callback accepts the class as first and
only parameter and has to return ``true`` if the corresponding file
was found and included.
.. note::
Loaders have to fail silently, if a class is not found even if it
matches for example the namespace prefix of that loader. Never is a
loader to throw a warning or exception if the loading failed
otherwise parsing doc block annotations will become a huge pain.
A sample loader callback could look like:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\ClassLoader\UniversalClassLoader;
AnnotationRegistry::registerLoader(function($class) {
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
if (file_exists("/my/base/path/" . $file)) {
// file_exists() makes sure that the loader fails silently
require "/my/base/path/" . $file;
}
});
$loader = new UniversalClassLoader();
AnnotationRegistry::registerLoader(array($loader, "loadClass"));
Ignoring missing exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default an exception is thrown from the ``AnnotationReader`` if an
annotation was found that:
- is not part of the list of ignored "documentation annotations";
- was not imported through a use statement;
- is not a fully qualified class that exists.
You can disable this behavior for specific names if your docblocks do
not follow strict requirements:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
AnnotationReader::addGlobalIgnoredName('foo');
PHP Imports
~~~~~~~~~~~
By default the annotation reader parses the use-statement of a php file
to gain access to the import rules and register them for the annotation
processing. Only if you are using PHP Imports can you validate the
correct usage of annotations and throw exceptions if you misspelled an
annotation. This mechanism is enabled by default.
To ease the upgrade path, we still allow you to disable this mechanism.
Note however that we will remove this in future versions:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setEnabledPhpImports(false);

View File

@ -0,0 +1,443 @@
Custom Annotation Classes
=========================
If you want to define your own annotations, you just have to group them
in a namespace and register this namespace in the ``AnnotationRegistry``.
Annotation classes have to contain a class-level docblock with the text
``@Annotation``:
.. code-block:: php
namespace MyCompany\Annotations;
/** @Annotation */
class Bar
{
// some code
}
Inject annotation values
------------------------
The annotation parser checks if the annotation constructor has arguments,
if so then it will pass the value array, otherwise it will try to inject
values into public properties directly:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
*
* Some Annotation using a constructor
*/
class Bar
{
private $foo;
public function __construct(array $values)
{
$this->foo = $values['foo'];
}
}
/**
* @Annotation
*
* Some Annotation without a constructor
*/
class Foo
{
public $bar;
}
Optional: Constructors with Named Parameters
--------------------------------------------
Starting with Annotations v1.11 a new annotation instantiation strategy
is available that aims at compatibility of Annotation classes with the PHP 8
attribute feature. You need to declare a constructor with regular parameter
names that match the named arguments in the annotation syntax.
To enable this feature, you can tag your annotation class with
``@NamedArgumentConstructor`` (available from v1.12) or implement the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
(available from v1.11 and deprecated as of v1.12).
When using the ``@NamedArgumentConstructor`` tag, the first argument of the
constructor is considered as the default one.
Usage with the ``@NamedArgumentContrustor`` tag
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(string $foo)
{
$this->foo = $foo;
}
}
/** Usable with @Bar(foo="baz") */
/** Usable with @Bar("baz") */
In combination with PHP 8's constructor property promotion feature
you can simplify this to:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
public function __construct(private string $foo) {}
}
Usage with the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation``
interface (v1.11, deprecated as of v1.12):
.. code-block:: php
namespace MyCompany\Annotations;
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
/** @Annotation */
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(private string $foo) {}
}
/** Usable with @Bar(foo="baz") */
Annotation Target
-----------------
``@Target`` indicates the kinds of class elements to which an annotation
type is applicable. Then you could define one or more targets:
- ``CLASS`` Allowed in class docblocks
- ``PROPERTY`` Allowed in property docblocks
- ``METHOD`` Allowed in the method docblocks
- ``FUNCTION`` Allowed in function dockblocks
- ``ALL`` Allowed in class, property, method and function docblocks
- ``ANNOTATION`` Allowed inside other annotations
If the annotations is not allowed in the current context, an
``AnnotationException`` is thrown.
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
// some code
}
/**
* @Annotation
* @Target("CLASS")
*/
class Foo
{
// some code
}
Attribute types
---------------
The annotation parser checks the given parameters using the phpdoc
annotation ``@var``, The data type could be validated using the ``@var``
annotation on the annotation properties or using the ``@Attributes`` and
``@Attribute`` annotations.
If the data type does not match you get an ``AnnotationException``
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
/** @var mixed */
public $mixed;
/** @var boolean */
public $boolean;
/** @var bool */
public $bool;
/** @var float */
public $float;
/** @var string */
public $string;
/** @var integer */
public $integer;
/** @var array */
public $array;
/** @var SomeAnnotationClass */
public $annotation;
/** @var array<integer> */
public $arrayOfIntegers;
/** @var array<SomeAnnotationClass> */
public $arrayOfAnnotations;
}
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
* @Attributes({
* @Attribute("stringProperty", type = "string"),
* @Attribute("annotProperty", type = "SomeAnnotationClass"),
* })
*/
class Foo
{
public function __construct(array $values)
{
$this->stringProperty = $values['stringProperty'];
$this->annotProperty = $values['annotProperty'];
}
// some code
}
Annotation Required
-------------------
``@Required`` indicates that the field must be specified when the
annotation is used. If it is not used you get an ``AnnotationException``
stating that this value can not be null.
Declaring a required field:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Foo
{
/** @Required */
public $requiredField;
}
Usage:
.. code-block:: php
/** @Foo(requiredField="value") */
public $direction; // Valid
/** @Foo */
public $direction; // Required field missing, throws an AnnotationException
Enumerated values
-----------------
- An annotation property marked with ``@Enum`` is a field that accepts a
fixed set of scalar values.
- You should use ``@Enum`` fields any time you need to represent fixed
values.
- The annotation parser checks the given value and throws an
``AnnotationException`` if the value does not match.
Declaring an enumerated property:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Direction
{
/**
* @Enum({"NORTH", "SOUTH", "EAST", "WEST"})
*/
public $value;
}
Annotation usage:
.. code-block:: php
/** @Direction("NORTH") */
public $direction; // Valid value
/** @Direction("NORTHEAST") */
public $direction; // Invalid value, throws an AnnotationException
Constants
---------
The use of constants and class constants is available on the annotations
parser.
The following usages are allowed:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
use MyCompany\Entity\SomeClass;
/**
* @Foo(PHP_EOL)
* @Bar(Bar::FOO)
* @Foo({SomeClass::FOO, SomeClass::BAR})
* @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE})
*/
class User
{
}
Be careful with constants and the cache !
.. note::
The cached reader will not re-evaluate each time an annotation is
loaded from cache. When a constant is changed the cache must be
cleaned.
Usage
-----
Using the library API is simple. Using the annotations described in the
previous section, you can now annotate other classes with your
annotations:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
/**
* @Foo(bar="foo")
* @Bar(foo="bar")
*/
class User
{
}
Now we can write a script to get the annotations above:
.. code-block:: php
$reflClass = new ReflectionClass('MyCompany\Entity\User');
$classAnnotations = $reader->getClassAnnotations($reflClass);
foreach ($classAnnotations AS $annot) {
if ($annot instanceof \MyCompany\Annotations\Foo) {
echo $annot->bar; // prints "foo";
} else if ($annot instanceof \MyCompany\Annotations\Bar) {
echo $annot->foo; // prints "bar";
}
}
You have a complete API for retrieving annotation class instances from a
class, property or method docblock:
Reader API
~~~~~~~~~~
Access all annotations of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotations(\ReflectionClass $class);
Access one annotation of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotation(\ReflectionClass $class, $annotationName);
Access all annotations of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotations(\ReflectionMethod $method);
Access one annotation of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
Access all annotations of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotations(\ReflectionProperty $property);
Access one annotation of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
Access all annotations of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotations(\ReflectionFunction $property);
Access one annotation of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName);

View File

@ -0,0 +1,101 @@
Introduction
============
Doctrine Annotations allows to implement custom annotation
functionality for PHP classes and functions.
.. code-block:: php
class Foo
{
/**
* @MyAnnotation(myProperty="value")
*/
private $bar;
}
Annotations aren't implemented in PHP itself which is why this component
offers a way to use the PHP doc-blocks as a place for the well known
annotation syntax using the ``@`` char.
Annotations in Doctrine are used for the ORM configuration to build the
class mapping, but it can be used in other projects for other purposes
too.
Installation
============
You can install the Annotation component with composer:
.. code-block::
  $ composer require doctrine/annotations
Create an annotation class
==========================
An annotation class is a representation of the later used annotation
configuration in classes. The annotation class of the previous example
looks like this:
.. code-block:: php
/**
* @Annotation
*/
final class MyAnnotation
{
public $myProperty;
}
The annotation class is declared as an annotation by ``@Annotation``.
:ref:`Read more about custom annotations. <custom>`
Reading annotations
===================
The access to the annotations happens by reflection of the class or function
containing them. There are multiple reader-classes implementing the
``Doctrine\Common\Annotations\Reader`` interface, that can access the
annotations of a class. A common one is
``Doctrine\Common\Annotations\AnnotationReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
// Deprecated and will be removed in 2.0 but currently needed
AnnotationRegistry::registerLoader('class_exists');
$reflectionClass = new ReflectionClass(Foo::class);
$property = $reflectionClass->getProperty('bar');
$reader = new AnnotationReader();
$myAnnotation = $reader->getPropertyAnnotation(
$property,
MyAnnotation::class
);
echo $myAnnotation->myProperty; // result: "value"
Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works
if you already have an autoloader configured (i.e. composer autoloader).
Otherwise, :ref:`please take a look to the other annotation autoload mechanisms <annotations>`.
A reader has multiple methods to access the annotations of a class or
function.
:ref:`Read more about handling annotations. <annotations>`
IDE Support
-----------
Some IDEs already provide support for annotations:
- Eclipse via the `Symfony2 Plugin <http://symfony.dubture.com/>`_
- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_
.. _Read more about handling annotations.: annotations
.. _Read more about custom annotations.: custom

View File

@ -0,0 +1,6 @@
.. toctree::
:depth: 3
index
annotations
custom

View File

@ -1,47 +1,27 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use BadMethodCallException;
use function sprintf;
/**
* Annotations class.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Annotation
{
/**
* Value property. Common among all derived classes.
*
* @var string
* @var mixed
*/
public $value;
/**
* Constructor.
*
* @param array $data Key-value for properties to be defined in this class.
* @param array<string, mixed> $data Key-value for properties to be defined in this class.
*/
public final function __construct(array $data)
final public function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
@ -53,12 +33,12 @@ class Annotation
*
* @param string $name Unknown property name.
*
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __get($name)
{
throw new \BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
@ -68,12 +48,12 @@ class Annotation
* @param string $name Unknown property name.
* @param mixed $value Property value.
*
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __set($name, $value)
{
throw new \BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
}

View File

@ -1,47 +1,21 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the attribute type during the parsing process.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Attribute
{
/**
* @var string
*/
/** @var string */
public $name;
/**
* @var string
*/
/** @var string */
public $type;
/**
* @var boolean
*/
/** @var bool */
public $required = false;
}

View File

@ -1,37 +1,15 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the types of all declared attributes during the parsing process.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Attributes
{
/**
* @var array<Doctrine\Common\Annotations\Annotation\Attribute>
*/
/** @var array<Attribute> */
public $value;
}

View File

@ -1,32 +1,20 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function get_class;
use function gettype;
use function in_array;
use function is_object;
use function is_scalar;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the available values during the parsing process.
*
* @since 2.4
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
* @Attributes({
* @Attribute("value", required = true, type = "array"),
@ -35,34 +23,30 @@ namespace Doctrine\Common\Annotations\Annotation;
*/
final class Enum
{
/**
* @var array
*/
/** @phpstan-var list<scalar> */
public $value;
/**
* Literal target declaration.
*
* @var array
* @var mixed[]
*/
public $literal;
/**
* Annotation constructor.
* @throws InvalidArgumentException
*
* @param array $values
*
* @throws \InvalidArgumentException
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
*/
public function __construct(array $values)
{
if ( ! isset($values['literal'])) {
$values['literal'] = array();
if (! isset($values['literal'])) {
$values['literal'] = [];
}
foreach ($values['value'] as $var) {
if( ! is_scalar($var)) {
throw new \InvalidArgumentException(sprintf(
if (! is_scalar($var)) {
throw new InvalidArgumentException(sprintf(
'@Enum supports only scalar values "%s" given.',
is_object($var) ? get_class($var) : gettype($var)
));
@ -70,15 +54,16 @@ final class Enum
}
foreach ($values['literal'] as $key => $var) {
if( ! in_array($key, $values['value'])) {
throw new \InvalidArgumentException(sprintf(
if (! in_array($key, $values['value'])) {
throw new InvalidArgumentException(sprintf(
'Undefined enumerator value "%s" for literal "%s".',
$key , $var
$key,
$var
));
}
}
$this->value = $values['value'];
$this->literal = $values['literal'];
$this->value = $values['value'];
$this->literal = $values['literal'];
}
}

View File

@ -1,52 +1,41 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
use RuntimeException;
use function is_array;
use function is_string;
use function json_encode;
use function sprintf;
/**
* Annotation that can be used to signal to the parser to ignore specific
* annotations during the parsing process.
*
* @Annotation
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class IgnoreAnnotation
{
/**
* @var array
*/
/** @phpstan-var list<string> */
public $names;
/**
* Constructor.
* @throws RuntimeException
*
* @param array $values
*
* @throws \RuntimeException
* @phpstan-param array{value: string|list<string>} $values
*/
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = array($values['value']);
$values['value'] = [$values['value']];
}
if (!is_array($values['value'])) {
throw new \RuntimeException(sprintf('@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value'])));
if (! is_array($values['value'])) {
throw new RuntimeException(sprintf(
'@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.',
json_encode($values['value'])
));
}
$this->names = $values['value'];

View File

@ -0,0 +1,13 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that indicates that the annotated class should be constructed with a named argument call.
*
* @Annotation
* @Target("CLASS")
*/
final class NamedArgumentConstructor
{
}

View File

@ -1,31 +1,11 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check if that attribute is required during the parsing process.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Required

View File

@ -1,89 +1,79 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function array_keys;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_object;
use function is_string;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the annotation target during the parsing process.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Target
{
const TARGET_CLASS = 1;
const TARGET_METHOD = 2;
const TARGET_PROPERTY = 4;
const TARGET_ANNOTATION = 8;
const TARGET_ALL = 15;
public const TARGET_CLASS = 1;
public const TARGET_METHOD = 2;
public const TARGET_PROPERTY = 4;
public const TARGET_ANNOTATION = 8;
public const TARGET_FUNCTION = 16;
public const TARGET_ALL = 31;
/**
* @var array
*/
private static $map = array(
/** @var array<string, int> */
private static $map = [
'ALL' => self::TARGET_ALL,
'CLASS' => self::TARGET_CLASS,
'METHOD' => self::TARGET_METHOD,
'PROPERTY' => self::TARGET_PROPERTY,
'FUNCTION' => self::TARGET_FUNCTION,
'ANNOTATION' => self::TARGET_ANNOTATION,
);
];
/**
* @var array
*/
/** @phpstan-var list<string> */
public $value;
/**
* Targets as bitmask.
*
* @var integer
* @var int
*/
public $targets;
/**
* Literal target declaration.
*
* @var integer
* @var string
*/
public $literal;
/**
* Annotation constructor.
* @throws InvalidArgumentException
*
* @param array $values
*
* @throws \InvalidArgumentException
* @phpstan-param array{value?: string|list<string>} $values
*/
public function __construct(array $values)
{
if (!isset($values['value'])){
if (! isset($values['value'])) {
$values['value'] = null;
}
if (is_string($values['value'])){
$values['value'] = array($values['value']);
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (!is_array($values['value'])){
throw new \InvalidArgumentException(
sprintf('@Target expects either a string value, or an array of strings, "%s" given.',
if (! is_array($values['value'])) {
throw new InvalidArgumentException(
sprintf(
'@Target expects either a string value, or an array of strings, "%s" given.',
is_object($values['value']) ? get_class($values['value']) : gettype($values['value'])
)
);
@ -91,17 +81,21 @@ final class Target
$bitmask = 0;
foreach ($values['value'] as $literal) {
if(!isset(self::$map[$literal])){
throw new \InvalidArgumentException(
sprintf('Invalid Target "%s". Available targets: [%s]',
$literal, implode(', ', array_keys(self::$map)))
if (! isset(self::$map[$literal])) {
throw new InvalidArgumentException(
sprintf(
'Invalid Target "%s". Available targets: [%s]',
$literal,
implode(', ', array_keys(self::$map))
)
);
}
$bitmask |= self::$map[$literal];
}
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
}
}

View File

@ -1,34 +1,19 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use Exception;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function sprintf;
/**
* Description of AnnotationException
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class AnnotationException extends \Exception
class AnnotationException extends Exception
{
/**
* Creates a new AnnotationException describing a Syntax error.
@ -58,8 +43,6 @@ class AnnotationException extends \Exception
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
*
* @since 2.2
*
* @param string $message
*
* @return AnnotationException
@ -72,8 +55,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a type error.
*
* @since 1.1
*
* @param string $message
*
* @return AnnotationException
@ -86,8 +67,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @since 2.3
*
* @param string $identifier
* @param string $context
*
@ -105,8 +84,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @since 2.2
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
@ -130,8 +107,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing an required error of an attribute.
*
* @since 2.2
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
@ -153,21 +128,20 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a invalid enummerator.
*
* @since 2.4
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param array $available
* @param mixed $given
*
* @return AnnotationException
*
* @phpstan-param list<string> $available
*/
public static function enumeratorError($attributeName, $annotationName, $context, $available, $given)
{
return new self(sprintf(
'[Enum Error] Attribute "%s" of @%s declared on %s accept only [%s], but got %s.',
$attributeName,
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
$attributeName,
$annotationName,
$context,
implode(', ', $available),
@ -181,7 +155,7 @@ class AnnotationException extends \Exception
public static function optimizerPlusSaveComments()
{
return new self(
"You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1."
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
);
}
@ -191,7 +165,7 @@ class AnnotationException extends \Exception
public static function optimizerPlusLoadComments()
{
return new self(
"You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1."
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
);
}
}

View File

@ -1,122 +1,57 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use function array_merge;
use function class_exists;
use function extension_loaded;
use function ini_get;
/**
* A reader for docblock annotations.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AnnotationReader implements Reader
{
/**
* Global map for imports.
*
* @var array
* @var array<string, class-string>
*/
private static $globalImports = array(
'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation',
);
private static $globalImports = [
'ignoreannotation' => Annotation\IgnoreAnnotation::class,
];
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array
* @var array<string, true>
*/
private static $globalIgnoredNames = array(
// Annotation tags
'Annotation' => true, 'Attribute' => true, 'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
// Widely used tags (but not existent in phpdoc)
'fix' => true , 'fixme' => true,
'override' => true,
// PHPDocumentor 1 tags
'abstract'=> true, 'access'=> true,
'code' => true,
'deprec'=> true,
'endcode' => true, 'exception'=> true,
'final'=> true,
'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true,
'magic' => true,
'name'=> true,
'toc' => true, 'tutorial'=> true,
'private' => true,
'static'=> true, 'staticvar'=> true, 'staticVar'=> true,
'throw' => true,
// PHPDocumentor 2 tags.
'api' => true, 'author'=> true,
'category'=> true, 'copyright'=> true,
'deprecated'=> true,
'example'=> true,
'filesource'=> true,
'global'=> true,
'ignore'=> true, /* Can we enable this? 'index' => true, */ 'internal'=> true,
'license'=> true, 'link'=> true,
'method' => true,
'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true,
'return'=> true,
'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true,
'throws'=> true, 'todo'=> true, 'TODO'=> true,
'usedby'=> true, 'uses' => true,
'var'=> true, 'version'=> true,
// PHPUnit tags
'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true,
// PHPCheckStyle
'SuppressWarnings' => true,
// PHPStorm
'noinspection' => true,
// PEAR
'package_version' => true,
// PlantUML
'startuml' => true, 'enduml' => true,
);
private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array
* @var array<string, true>
*/
private static $globalIgnoredNamespaces = array();
private static $globalIgnoredNamespaces = [];
/**
* Add a new annotation to the globally ignored annotation names with regard to exception handling.
*
* @param string $name
*/
static public function addGlobalIgnoredName($name)
public static function addGlobalIgnoredName($name)
{
self::$globalIgnoredNames[$name] = true;
}
@ -126,7 +61,7 @@ class AnnotationReader implements Reader
*
* @param string $namespace
*/
static public function addGlobalIgnoredNamespace($namespace)
public static function addGlobalIgnoredNamespace($namespace)
{
self::$globalIgnoredNamespaces[$namespace] = true;
}
@ -134,75 +69,68 @@ class AnnotationReader implements Reader
/**
* Annotations parser.
*
* @var \Doctrine\Common\Annotations\DocParser
* @var DocParser
*/
private $parser;
/**
* Annotations parser used to collect parsing metadata.
*
* @var \Doctrine\Common\Annotations\DocParser
* @var DocParser
*/
private $preParser;
/**
* PHP parser used to collect imports.
*
* @var \Doctrine\Common\Annotations\PhpParser
* @var PhpParser
*/
private $phpParser;
/**
* In-memory cache mechanism to store imported annotations per class.
*
* @var array
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
*/
private $imports = array();
private $imports = [];
/**
* In-memory cache mechanism to store ignored annotations per class.
*
* @var array
* @psalm-var array<'class'|'function', array<string, array<string, true>>>
*/
private $ignoredAnnotationNames = array();
private $ignoredAnnotationNames = [];
/**
* Constructor.
*
* Initializes a new AnnotationReader.
*
* @param DocParser $parser
* @throws AnnotationException
*/
public function __construct(DocParser $parser = null)
public function __construct(?DocParser $parser = null)
{
if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
if (
extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' ||
ini_get('opcache.save_comments') === '0')
) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (PHP_VERSION_ID < 70000) {
if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.load_comments') === "0" || ini_get('opcache.load_comments') === "0")) {
throw AnnotationException::optimizerPlusLoadComments();
}
if (extension_loaded('Zend OPcache') && ini_get('opcache.load_comments') == 0) {
throw AnnotationException::optimizerPlusLoadComments();
}
}
AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php');
// Make sure that the IgnoreAnnotation annotation is loaded
class_exists(IgnoreAnnotation::class);
$this->parser = $parser ?: new DocParser();
$this->preParser = new DocParser;
$this->preParser = new DocParser();
$this->preParser->setImports(self::$globalImports);
$this->preParser->setIgnoreNotImportedAnnotations(true);
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
$this->phpParser = new PhpParser;
$this->phpParser = new PhpParser();
}
/**
@ -211,7 +139,7 @@ class AnnotationReader implements Reader
public function getClassAnnotations(ReflectionClass $class)
{
$this->parser->setTarget(Target::TARGET_CLASS);
$this->parser->setImports($this->getClassImports($class));
$this->parser->setImports($this->getImports($class));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
@ -240,7 +168,7 @@ class AnnotationReader implements Reader
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$context = 'property ' . $class->getName() . "::\$" . $property->getName();
$context = 'property ' . $class->getName() . '::$' . $property->getName();
$this->parser->setTarget(Target::TARGET_PROPERTY);
$this->parser->setImports($this->getPropertyImports($property));
@ -299,66 +227,103 @@ class AnnotationReader implements Reader
}
/**
* Returns the ignored annotations for the given class.
* Gets the annotations applied to a function.
*
* @param \ReflectionClass $class
*
* @return array
* @phpstan-return list<object> An array of Annotations.
*/
private function getIgnoredAnnotationNames(ReflectionClass $class)
public function getFunctionAnnotations(ReflectionFunction $function): array
{
$name = $class->getName();
if (isset($this->ignoredAnnotationNames[$name])) {
return $this->ignoredAnnotationNames[$name];
}
$context = 'function ' . $function->getName();
$this->collectParsingMetadata($class);
$this->parser->setTarget(Target::TARGET_FUNCTION);
$this->parser->setImports($this->getImports($function));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->ignoredAnnotationNames[$name];
return $this->parser->parse($function->getDocComment(), $context);
}
/**
* Retrieves imports.
* Gets a function annotation.
*
* @param \ReflectionClass $class
*
* @return array
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
*/
private function getClassImports(ReflectionClass $class)
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
{
$name = $class->getName();
if (isset($this->imports[$name])) {
return $this->imports[$name];
$annotations = $this->getFunctionAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
$this->collectParsingMetadata($class);
return null;
}
return $this->imports[$name];
/**
* Returns the ignored annotations for the given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, true>
*/
private function getIgnoredAnnotationNames($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->ignoredAnnotationNames[$type][$name])) {
return $this->ignoredAnnotationNames[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->ignoredAnnotationNames[$type][$name];
}
/**
* Retrieves imports for a class or a function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, class-string>
*/
private function getImports($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->imports[$type][$name])) {
return $this->imports[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->imports[$type][$name];
}
/**
* Retrieves imports for methods.
*
* @param \ReflectionMethod $method
*
* @return array
* @return array<string, class-string>
*/
private function getMethodImports(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$classImports = $this->getClassImports($class);
if (!method_exists($class, 'getTraits')) {
return $classImports;
}
$class = $method->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = array();
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if ($trait->hasMethod($method->getName())
&& $trait->getFileName() === $method->getFileName()
if (
! $trait->hasMethod($method->getName())
|| $trait->getFileName() !== $method->getFileName()
) {
$traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
@ -367,55 +332,58 @@ class AnnotationReader implements Reader
/**
* Retrieves imports for properties.
*
* @param \ReflectionProperty $property
*
* @return array
* @return array<string, class-string>
*/
private function getPropertyImports(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$classImports = $this->getClassImports($class);
if (!method_exists($class, 'getTraits')) {
return $classImports;
}
$class = $property->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = array();
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if ($trait->hasProperty($property->getName())) {
$traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
if (! $trait->hasProperty($property->getName())) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Collects parsing metadata for a given class.
* Collects parsing metadata for a given class or function.
*
* @param \ReflectionClass $class
* @param ReflectionClass|ReflectionFunction $reflection
*/
private function collectParsingMetadata(ReflectionClass $class)
private function collectParsingMetadata($reflection): void
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
$ignoredAnnotationNames = self::$globalIgnoredNames;
$annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
foreach ($annotations as $annotation) {
if ($annotation instanceof IgnoreAnnotation) {
foreach ($annotation->names AS $annot) {
$ignoredAnnotationNames[$annot] = true;
}
if (! ($annotation instanceof IgnoreAnnotation)) {
continue;
}
foreach ($annotation->names as $annot) {
$ignoredAnnotationNames[$annot] = true;
}
}
$name = $class->getName();
$this->imports[$name] = array_merge(
$this->imports[$type][$name] = array_merge(
self::$globalImports,
$this->phpParser->parseClass($class),
array('__NAMESPACE__' => $class->getNamespaceName())
$this->phpParser->parseUseStatements($reflection),
[
'__NAMESPACE__' => $reflection->getNamespaceName(),
'self' => $name,
]
);
$this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
}
}

View File

@ -1,27 +1,18 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
/**
* AnnotationRegistry.
*/
use function array_key_exists;
use function array_merge;
use function class_exists;
use function in_array;
use function is_file;
use function str_replace;
use function stream_resolve_include_path;
use function strpos;
use const DIRECTORY_SEPARATOR;
final class AnnotationRegistry
{
/**
@ -32,35 +23,49 @@ final class AnnotationRegistry
*
* This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own.
*
* @var array
* @var string[][]|string[]|null[]
*/
static private $autoloadNamespaces = array();
private static $autoloadNamespaces = [];
/**
* A map of autoloader callables.
*
* @var array
* @var callable[]
*/
static private $loaders = array();
private static $loaders = [];
/**
* @return void
* An array of classes which cannot be found
*
* @var null[] indexed by class name
*/
static public function reset()
private static $failedToAutoload = [];
/**
* Whenever registerFile() was used. Disables use of standard autoloader.
*
* @var bool
*/
private static $registerFileUsed = false;
public static function reset(): void
{
self::$autoloadNamespaces = array();
self::$loaders = array();
self::$autoloadNamespaces = [];
self::$loaders = [];
self::$failedToAutoload = [];
self::$registerFileUsed = false;
}
/**
* Registers file.
*
* @param string $file
*
* @return void
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
static public function registerFile($file)
public static function registerFile(string $file): void
{
self::$registerFileUsed = true;
require_once $file;
}
@ -69,12 +74,12 @@ final class AnnotationRegistry
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @param string $namespace
* @param string|array|null $dirs
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @return void
* @phpstan-param string|list<string>|null $dirs
*/
static public function registerAutoloadNamespace($namespace, $dirs = null)
public static function registerAutoloadNamespace(string $namespace, $dirs = null): void
{
self::$autoloadNamespaces[$namespace] = $dirs;
}
@ -84,11 +89,12 @@ final class AnnotationRegistry
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @param array $namespaces
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @return void
* @param string[][]|string[]|null[] $namespaces indexed by namespace name
*/
static public function registerAutoloadNamespaces(array $namespaces)
public static function registerAutoloadNamespaces(array $namespaces): void
{
self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces);
}
@ -99,53 +105,86 @@ final class AnnotationRegistry
* NOTE: These class loaders HAVE to be silent when a class was not found!
* IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class.
*
* @param callable $callable
*
* @return void
*
* @throws \InvalidArgumentException
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
static public function registerLoader($callable)
public static function registerLoader(callable $callable): void
{
if (!is_callable($callable)) {
throw new \InvalidArgumentException("A callable is expected in AnnotationRegistry::registerLoader().");
// Reset our static cache now that we have a new loader to work with
self::$failedToAutoload = [];
self::$loaders[] = $callable;
}
/**
* Registers an autoloading callable for annotations, if it is not already registered
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
public static function registerUniqueLoader(callable $callable): void
{
if (in_array($callable, self::$loaders, true)) {
return;
}
self::$loaders[] = $callable;
self::registerLoader($callable);
}
/**
* Autoloads an annotation class silently.
*
* @param string $class
*
* @return boolean
*/
static public function loadAnnotationClass($class)
public static function loadAnnotationClass(string $class): bool
{
foreach (self::$autoloadNamespaces AS $namespace => $dirs) {
if (strpos($class, $namespace) === 0) {
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
if ($dirs === null) {
if ($path = stream_resolve_include_path($file)) {
require $path;
if (class_exists($class, false)) {
return true;
}
if (array_key_exists($class, self::$failedToAutoload)) {
return false;
}
foreach (self::$autoloadNamespaces as $namespace => $dirs) {
if (strpos($class, $namespace) !== 0) {
continue;
}
$file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if ($dirs === null) {
$path = stream_resolve_include_path($file);
if ($path) {
require $path;
return true;
}
} else {
foreach ((array) $dirs as $dir) {
if (is_file($dir . DIRECTORY_SEPARATOR . $file)) {
require $dir . DIRECTORY_SEPARATOR . $file;
return true;
}
} else {
foreach((array)$dirs AS $dir) {
if (is_file($dir . DIRECTORY_SEPARATOR . $file)) {
require $dir . DIRECTORY_SEPARATOR . $file;
return true;
}
}
}
}
}
foreach (self::$loaders AS $loader) {
if (call_user_func($loader, $class) === true) {
foreach (self::$loaders as $loader) {
if ($loader($class) === true) {
return true;
}
}
if (
self::$loaders === [] &&
self::$autoloadNamespaces === [] &&
self::$registerFileUsed === false &&
class_exists($class)
) {
return true;
}
self::$failedToAutoload[$class] = null;
return false;
}
}

View File

@ -1,67 +1,47 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Cache\Cache;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function array_map;
use function array_merge;
use function assert;
use function filemtime;
use function max;
use function time;
/**
* A cache aware annotation reader.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
final class CachedReader implements Reader
{
/**
* @var Reader
*/
/** @var Reader */
private $delegate;
/**
* @var Cache
*/
/** @var Cache */
private $cache;
/**
* @var boolean
*/
/** @var bool */
private $debug;
/**
* @var array
*/
private $loadedAnnotations = array();
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
/**
* Constructor.
*
* @param Reader $reader
* @param Cache $cache
* @param bool $debug
* @param bool $debug
*/
public function __construct(Reader $reader, Cache $cache, $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (boolean) $debug;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
@ -75,7 +55,8 @@ final class CachedReader implements Reader
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getClassAnnotations($class);
$this->saveToCache($cacheKey, $annots);
}
@ -100,16 +81,17 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName().'$'.$property->getName();
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getPropertyAnnotations($property);
$this->saveToCache($cacheKey, $annots);
}
@ -120,7 +102,7 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
@ -134,16 +116,17 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName().'#'.$method->getName();
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getMethodAnnotations($method);
$this->saveToCache($cacheKey, $annots);
}
@ -154,7 +137,7 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
@ -172,21 +155,22 @@ final class CachedReader implements Reader
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = array();
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/**
* Fetches a value from the cache.
*
* @param string $cacheKey The cache key.
* @param ReflectionClass $class The related class.
* @param string $cacheKey The cache key.
*
* @return mixed The cached value or false when the value is not in cache.
*/
private function fetchFromCache($cacheKey, ReflectionClass $class)
{
if (($data = $this->cache->fetch($cacheKey)) !== false) {
if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) {
$data = $this->cache->fetch($cacheKey);
if ($data !== false) {
if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) {
return $data;
}
}
@ -205,58 +189,76 @@ final class CachedReader implements Reader
private function saveToCache($cacheKey, $value)
{
$this->cache->save($cacheKey, $value);
if ($this->debug) {
$this->cache->save('[C]'.$cacheKey, time());
if (! $this->debug) {
return;
}
$this->cache->save('[C]' . $cacheKey, time());
}
/**
* Checks if the cache is fresh.
*
* @param string $cacheKey
* @param ReflectionClass $class
* @param string $cacheKey
*
* @return boolean
* @return bool
*/
private function isCacheFresh($cacheKey, ReflectionClass $class)
{
if (null === $lastModification = $this->getLastModification($class)) {
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification;
return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification;
}
/**
* Returns the time the class was last modified, testing traits and parents
*
* @param ReflectionClass $class
* @return int
*/
private function getLastModification(ReflectionClass $class)
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
$parent = $class->getParentClass();
return max(array_merge(
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename ? filemtime($filename) : 0],
array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()),
array_map([$this, 'getLastModification'], $class->getInterfaces()),
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
array_map(function (ReflectionClass $class): int {
return $this->getLastModification($class);
}, $class->getInterfaces()),
$parent ? [$this->getLastModification($parent)] : []
));
assert($lastModification !== false);
return $this->loadedFilemtimes[$filename] = $lastModification;
}
/**
* @param ReflectionClass $reflectionTrait
* @return int
*/
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait)
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
return max(array_merge(
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName ? filemtime($fileName) : 0],
array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits())
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@ -1,61 +1,46 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Lexer\AbstractLexer;
use function ctype_alpha;
use function is_numeric;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
/**
* Simple lexer for docblock annotations.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class DocLexer extends AbstractLexer
{
const T_NONE = 1;
const T_INTEGER = 2;
const T_STRING = 3;
const T_FLOAT = 4;
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_FLOAT = 4;
// All tokens that are also identifiers should be >= 100
const T_IDENTIFIER = 100;
const T_AT = 101;
const T_CLOSE_CURLY_BRACES = 102;
const T_CLOSE_PARENTHESIS = 103;
const T_COMMA = 104;
const T_EQUALS = 105;
const T_FALSE = 106;
const T_NAMESPACE_SEPARATOR = 107;
const T_OPEN_CURLY_BRACES = 108;
const T_OPEN_PARENTHESIS = 109;
const T_TRUE = 110;
const T_NULL = 111;
const T_COLON = 112;
public const T_IDENTIFIER = 100;
public const T_AT = 101;
public const T_CLOSE_CURLY_BRACES = 102;
public const T_CLOSE_PARENTHESIS = 103;
public const T_COMMA = 104;
public const T_EQUALS = 105;
public const T_FALSE = 106;
public const T_NAMESPACE_SEPARATOR = 107;
public const T_OPEN_CURLY_BRACES = 108;
public const T_OPEN_PARENTHESIS = 109;
public const T_TRUE = 110;
public const T_NULL = 111;
public const T_COLON = 112;
public const T_MINUS = 113;
/**
* @var array
*/
protected $noCase = array(
/** @var array<string, int> */
protected $noCase = [
'@' => self::T_AT,
',' => self::T_COMMA,
'(' => self::T_OPEN_PARENTHESIS,
@ -64,28 +49,38 @@ final class DocLexer extends AbstractLexer
'}' => self::T_CLOSE_CURLY_BRACES,
'=' => self::T_EQUALS,
':' => self::T_COLON,
'\\' => self::T_NAMESPACE_SEPARATOR
);
'-' => self::T_MINUS,
'\\' => self::T_NAMESPACE_SEPARATOR,
];
/**
* @var array
*/
protected $withCase = array(
/** @var array<string, int> */
protected $withCase = [
'true' => self::T_TRUE,
'false' => self::T_FALSE,
'null' => self::T_NULL
);
'null' => self::T_NULL,
];
/**
* Whether the next token starts immediately, or if there were
* non-captured symbols before that
*/
public function nextTokenIsAdjacent(): bool
{
return $this->token === null
|| ($this->lookahead !== null
&& ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value']));
}
/**
* {@inheritdoc}
*/
protected function getCatchablePatterns()
{
return array(
return [
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:""|[^"])*+"',
);
];
}
/**
@ -93,7 +88,7 @@ final class DocLexer extends AbstractLexer
*/
protected function getNonCatchablePatterns()
{
return array('\s+', '\*+', '(.)');
return ['\s+', '\*+', '(.)'];
}
/**
@ -125,7 +120,7 @@ final class DocLexer extends AbstractLexer
// Checking numeric value
if (is_numeric($value)) {
return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
return strpos($value, '.') !== false || stripos($value, 'e') !== false
? self::T_FLOAT : self::T_INTEGER;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,84 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use RuntimeException;
use function chmod;
use function file_put_contents;
use function filemtime;
use function gettype;
use function is_dir;
use function is_file;
use function is_int;
use function is_writable;
use function mkdir;
use function rename;
use function rtrim;
use function serialize;
use function sha1;
use function sprintf;
use function strtr;
use function tempnam;
use function uniqid;
use function unlink;
use function var_export;
/**
* File cache reader for annotations.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*
* @deprecated the FileCacheReader is deprecated and will be removed
* in version 2.0.0 of doctrine/annotations. Please use the
* {@see \Doctrine\Common\Annotations\CachedReader} instead.
*/
class FileCacheReader implements Reader
{
/**
* @var Reader
*/
/** @var Reader */
private $reader;
/**
* @var string
*/
/** @var string */
private $dir;
/**
* @var bool
*/
/** @var bool */
private $debug;
/**
* @var array
*/
private $loadedAnnotations = array();
/** @phpstan-var array<string, list<object>> */
private $loadedAnnotations = [];
/**
* @var array
*/
private $classNameHashes = array();
/** @var array<string, string> */
private $classNameHashes = [];
/**
* @var int
*/
/** @var int */
private $umask;
/**
* Constructor.
* @param string $cacheDir
* @param bool $debug
* @param int $umask
*
* @param Reader $reader
* @param string $cacheDir
* @param boolean $debug
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002)
{
if ( ! is_int($umask)) {
throw new \InvalidArgumentException(sprintf(
if (! is_int($umask)) {
throw new InvalidArgumentException(sprintf(
'The parameter umask must be an integer, was: %s',
gettype($umask)
));
}
$this->reader = $reader;
$this->umask = $umask;
$this->umask = $umask;
if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777 & (~$this->umask), true)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir));
if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$cacheDir
));
}
$this->dir = rtrim($cacheDir, '\\/');
@ -93,31 +88,37 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotations(\ReflectionClass $class)
public function getClassAnnotations(ReflectionClass $class)
{
if ( ! isset($this->classNameHashes[$class->name])) {
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name];
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php';
if (!is_file($path)) {
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getClassAnnotations($class);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
if ($this->debug
&& (false !== $filename = $class->getFileName())
&& filemtime($path) < filemtime($filename)) {
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getClassAnnotations($class);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
@ -127,32 +128,38 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
if ( ! isset($this->classNameHashes[$class->name])) {
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name].'$'.$property->getName();
$key = $this->classNameHashes[$class->name] . '$' . $property->getName();
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php';
if (!is_file($path)) {
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getPropertyAnnotations($property);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
if ($this->debug
&& (false !== $filename = $class->getFilename())
&& filemtime($path) < filemtime($filename)) {
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getPropertyAnnotations($property);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
@ -162,32 +169,38 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
if ( ! isset($this->classNameHashes[$class->name])) {
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name].'#'.$method->getName();
$key = $this->classNameHashes[$class->name] . '#' . $method->getName();
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php';
if (!is_file($path)) {
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getMethodAnnotations($method);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
if ($this->debug
&& (false !== $filename = $class->getFilename())
&& filemtime($path) < filemtime($filename)) {
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getMethodAnnotations($method);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
@ -204,36 +217,48 @@ class FileCacheReader implements Reader
*/
private function saveCacheFile($path, $data)
{
if (!is_writable($this->dir)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.', $this->dir));
if (! is_writable($this->dir)) {
throw new InvalidArgumentException(sprintf(
<<<'EXCEPTION'
The directory "%s" is not writable. Both the webserver and the console user need access.
You can manage access rights for multiple users with "chmod +a".
If your system does not support this, check out the acl package.,
EXCEPTION
,
$this->dir
));
}
$tempfile = tempnam($this->dir, uniqid('', true));
if (false === $tempfile) {
throw new \RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir));
if ($tempfile === false) {
throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir));
}
@chmod($tempfile, 0666 & (~$this->umask));
$written = file_put_contents($tempfile, '<?php return unserialize('.var_export(serialize($data), true).');');
$written = file_put_contents(
$tempfile,
'<?php return unserialize(' . var_export(serialize($data), true) . ');'
);
if (false === $written) {
throw new \RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
if ($written === false) {
throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
}
@chmod($tempfile, 0666 & (~$this->umask));
if (false === rename($tempfile, $path)) {
if (rename($tempfile, $path) === false) {
@unlink($tempfile);
throw new \RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
}
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(\ReflectionClass $class, $annotationName)
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
@ -249,7 +274,7 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
@ -265,7 +290,7 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
@ -285,6 +310,6 @@ class FileCacheReader implements Reader
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = array();
$this->loadedAnnotations = [];
}
}

View File

@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Annotations;
/**
* A list of annotations that are implicitly ignored during the parsing process.
*
* All names are case sensitive.
*/
final class ImplicitlyIgnoredAnnotationNames
{
private const Reserved = [
'Annotation' => true,
'Attribute' => true,
'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
];
private const WidelyUsedNonStandard = [
'fix' => true,
'fixme' => true,
'override' => true,
];
private const PhpDocumentor1 = [
'abstract' => true,
'access' => true,
'code' => true,
'deprec' => true,
'endcode' => true,
'exception' => true,
'final' => true,
'ingroup' => true,
'inheritdoc' => true,
'inheritDoc' => true,
'magic' => true,
'name' => true,
'private' => true,
'static' => true,
'staticvar' => true,
'staticVar' => true,
'toc' => true,
'tutorial' => true,
'throw' => true,
];
private const PhpDocumentor2 = [
'api' => true,
'author' => true,
'category' => true,
'copyright' => true,
'deprecated' => true,
'example' => true,
'filesource' => true,
'global' => true,
'ignore' => true,
/* Can we enable this? 'index' => true, */
'internal' => true,
'license' => true,
'link' => true,
'method' => true,
'package' => true,
'param' => true,
'property' => true,
'property-read' => true,
'property-write' => true,
'return' => true,
'see' => true,
'since' => true,
'source' => true,
'subpackage' => true,
'throws' => true,
'todo' => true,
'TODO' => true,
'usedby' => true,
'uses' => true,
'var' => true,
'version' => true,
];
private const PHPUnit = [
'author' => true,
'after' => true,
'afterClass' => true,
'backupGlobals' => true,
'backupStaticAttributes' => true,
'before' => true,
'beforeClass' => true,
'codeCoverageIgnore' => true,
'codeCoverageIgnoreStart' => true,
'codeCoverageIgnoreEnd' => true,
'covers' => true,
'coversDefaultClass' => true,
'coversNothing' => true,
'dataProvider' => true,
'depends' => true,
'doesNotPerformAssertions' => true,
'expectedException' => true,
'expectedExceptionCode' => true,
'expectedExceptionMessage' => true,
'expectedExceptionMessageRegExp' => true,
'group' => true,
'large' => true,
'medium' => true,
'preserveGlobalState' => true,
'requires' => true,
'runTestsInSeparateProcesses' => true,
'runInSeparateProcess' => true,
'small' => true,
'test' => true,
'testdox' => true,
'testWith' => true,
'ticket' => true,
'uses' => true,
];
private const PhpCheckStyle = ['SuppressWarnings' => true];
private const PhpStorm = ['noinspection' => true];
private const PEAR = ['package_version' => true];
private const PlainUML = [
'startuml' => true,
'enduml' => true,
];
private const Symfony = ['experimental' => true];
private const PhpCodeSniffer = [
'codingStandardsIgnoreStart' => true,
'codingStandardsIgnoreEnd' => true,
];
private const SlevomatCodingStandard = ['phpcsSuppress' => true];
private const PhpStan = [
'extends' => true,
'implements' => true,
'template' => true,
'use' => true,
];
private const Phan = ['suppress' => true];
private const Rector = ['noRector' => true];
public const LIST = self::Reserved
+ self::WidelyUsedNonStandard
+ self::PhpDocumentor1
+ self::PhpDocumentor2
+ self::PHPUnit
+ self::PhpCheckStyle
+ self::PhpStorm
+ self::PEAR
+ self::PlainUML
+ self::Symfony
+ self::SlevomatCodingStandard
+ self::PhpCodeSniffer
+ self::PhpStan
+ self::Phan
+ self::Rector;
private function __construct()
{
}
}

View File

@ -1,41 +1,22 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function call_user_func_array;
use function get_class;
/**
* Allows the reader to be used in-place of Doctrine's reader.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class IndexedReader implements Reader
{
/**
* @var Reader
*/
/** @var Reader */
private $delegate;
/**
* Constructor.
*
* @param Reader $reader
*/
public function __construct(Reader $reader)
{
$this->delegate = $reader;
@ -44,9 +25,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotations(\ReflectionClass $class)
public function getClassAnnotations(ReflectionClass $class)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -57,7 +38,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotation(\ReflectionClass $class, $annotation)
public function getClassAnnotation(ReflectionClass $class, $annotation)
{
return $this->delegate->getClassAnnotation($class, $annotation);
}
@ -65,9 +46,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -78,7 +59,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotation)
public function getMethodAnnotation(ReflectionMethod $method, $annotation)
{
return $this->delegate->getMethodAnnotation($method, $annotation);
}
@ -86,9 +67,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -99,7 +80,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotation)
public function getPropertyAnnotation(ReflectionProperty $property, $annotation)
{
return $this->delegate->getPropertyAnnotation($property, $annotation);
}
@ -107,13 +88,13 @@ class IndexedReader implements Reader
/**
* Proxies all methods to the delegate.
*
* @param string $method
* @param array $args
* @param string $method
* @param mixed[] $args
*
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array(array($this->delegate, $method), $args);
return call_user_func_array([$this->delegate, $method], $args);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Doctrine\Common\Annotations;
/**
* Marker interface for PHP7/PHP8 compatible support
* for named arguments (and constructor property promotion).
*
* @deprecated Implementing this interface is deprecated
* Use the Annotation @NamedArgumentConstructor instead
*/
interface NamedArgumentConstructorAnnotation
{
}

View File

@ -1,85 +1,86 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionFunction;
use SplFileObject;
use function is_file;
use function method_exists;
use function preg_quote;
use function preg_replace;
/**
* Parses a file for namespaces/use/class declarations.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Kaps <christian.kaps@mohiva.com>
*/
final class PhpParser
{
/**
* Parses a class.
*
* @param \ReflectionClass $class A <code>ReflectionClass</code> object.
* @deprecated use parseUseStatements instead
*
* @return array A list with use statements in the form (Alias => FQN).
* @param ReflectionClass $class A <code>ReflectionClass</code> object.
*
* @return array<string, class-string> A list with use statements in the form (Alias => FQN).
*/
public function parseClass(\ReflectionClass $class)
public function parseClass(ReflectionClass $class)
{
if (method_exists($class, 'getUseStatements')) {
return $class->getUseStatements();
return $this->parseUseStatements($class);
}
/**
* Parse a class or function for use statements.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @psalm-return array<string, string> a list with use statements in the form (Alias => FQN).
*/
public function parseUseStatements($reflection): array
{
if (method_exists($reflection, 'getUseStatements')) {
return $reflection->getUseStatements();
}
if (false === $filename = $class->getFileName()) {
return array();
$filename = $reflection->getFileName();
if ($filename === false) {
return [];
}
$content = $this->getFileContent($filename, $class->getStartLine());
$content = $this->getFileContent($filename, $reflection->getStartLine());
if (null === $content) {
return array();
if ($content === null) {
return [];
}
$namespace = preg_quote($class->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$namespace = preg_quote($reflection->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
$statements = $tokenizer->parseUseStatements($class->getNamespaceName());
return $statements;
return $tokenizer->parseUseStatements($reflection->getNamespaceName());
}
/**
* Gets the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param integer $lineNumber The number of lines to read from file.
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
*
* @return string|null The content of the file or null if the file does not exist.
*/
private function getFileContent($filename, $lineNumber)
{
if ( ! is_file($filename)) {
if (! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (!$file->eof()) {
if ($lineCnt++ == $lineNumber) {
$file = new SplFileObject($filename);
while (! $file->eof()) {
if ($lineCnt++ === $lineNumber) {
break;
}

Some files were not shown because too many files have changed in this diff Show More