├── README ├── settings └── rest.ini.append.php ├── extension.xml ├── composer.json ├── classes ├── views │ ├── atom.php │ └── atom_decorator.php ├── view_controller.php ├── controllers │ ├── atom.php │ └── content.php ├── rest_provider.php └── models │ └── rest_content_model.php ├── tests ├── suite.php └── model_test.php └── LICENSE /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings/rest.ini.append.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /extension.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eZ REST API Provider LS 5 | dev-master 6 | Copyright (C) eZ Systems AS. All rights reserved. 7 | For full copyright and license information view LICENSE file provided with this software. 8 | 9 | 10 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ezsystems/ezprestapiprovider-ls", 3 | "description": "eZ Publish REST API v1 content provider", 4 | "type": "ezpublish-legacy-extension", 5 | "license": "GPL-2.0", 6 | "authors": [ 7 | { 8 | "name": "eZ Publish dev-team & eZ Community", 9 | "homepage": "https://github.com/ezsystems/ezprestapiprovider/contributors" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { 14 | "ezsystems/ezpublish-legacy-installer": "*" 15 | }, 16 | "extra": { 17 | "ezpublish-legacy-extension-name": "ezprestapiprovider" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /classes/views/atom.php: -------------------------------------------------------------------------------- 1 | content = new ezcMvcResultContent(); 19 | $result->content->type = "application/atom+xml"; 20 | $result->content->charset = "UTF-8"; 21 | } 22 | 23 | public function createZones( $layout ) 24 | { 25 | return array( 26 | new ezcMvcFeedViewHandler( "content", new ezpRestAtomDecorator, "atom" ) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /classes/view_controller.php: -------------------------------------------------------------------------------- 1 | controllerClass === 'ezpRestAtomController' ) 24 | { 25 | return new ezpRestAtomView( $request, $result ); 26 | } 27 | return new ezpRestJsonView( $request, $result ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/suite.php: -------------------------------------------------------------------------------- 1 | setName( "eZ Publish REST content services test suite" ); 18 | $this->addTestSuite( 'ezpRestContentServiceModelTest' ); 19 | } 20 | 21 | public static function suite() 22 | { 23 | return new self(); 24 | } 25 | 26 | public function setUp() 27 | { 28 | parent::setUp(); 29 | 30 | // make sure extension is enabled and settings are read 31 | ezpExtensionHelper::load( 'ezprestapiprovider' ); 32 | ezpExtensionHelper::load( 'rest' ); 33 | } 34 | 35 | public function tearDown() 36 | { 37 | ezpExtensionHelper::unload( 'ezprestapiprovider' ); 38 | ezpExtensionHelper::unload( 'rest' ); 39 | parent::tearDown(); 40 | } 41 | } 42 | 43 | ?> 44 | -------------------------------------------------------------------------------- /classes/views/atom_decorator.php: -------------------------------------------------------------------------------- 1 | generator = "eZ Publish"; 14 | $feed->updated = time(); 15 | $feed->author = "The Editor."; 16 | $feed->id = "node:f3e90596361e31d496d4026eb624c983:2"; 17 | $feed->title = "eZ Publish REST content sync feed for [Collection]"; 18 | } 19 | 20 | /** 21 | * Returns the name of the variable in the result object to decorate 22 | * 23 | * @return string 24 | */ 25 | public function getItemVariable() 26 | { 27 | return "collection"; 28 | } 29 | 30 | /** 31 | * Adds feed metadata pertaining to the item's data specified in $data 32 | * 33 | * @todo Add list of required metadata to add 34 | * 35 | * @param string $ezcFeedEntryElement 36 | * @param string $data 37 | * @return void 38 | */ 39 | public function decorateFeedItem( ezcFeedEntryElement $item, $data ) 40 | { 41 | $author = $item->add( "author" ); 42 | $author->name = $data["author"]; 43 | 44 | $link = $item->add( "link" ); 45 | $link->href = $data["nodeUrl"]; 46 | 47 | $item->title = $data["objectName"]; 48 | $item->updated = $data["modified"]; 49 | $item->published = $data["published"]; 50 | $item->id = "N/A"; 51 | $item->description = ""; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /classes/controllers/atom.php: -------------------------------------------------------------------------------- 1 | accept[] = ezpContentCriteria::location()->subtree( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 27 | 28 | $retData = array(); 29 | $baseUri = substr( $this->request->protocol, 0, strpos( $this->request->protocol, "-" ) ) . "://{$this->request->host}"; 30 | 31 | foreach ( ezpContentRepository::query( $crit ) as $node ) 32 | { 33 | $retData[] = array( 34 | "objectName" => $node->name, 35 | "author" => $node->owner->Name, 36 | "modified" => $node->dateModified, 37 | "published" => $node->datePublished, 38 | "classIdentifier" => $node->classIdentifier, 39 | "nodeUrl" => $baseUri . $this->getRouter()->generateUrl( 1, array( "nodeId" => $node->locations->node_id ) ) 40 | ); 41 | } 42 | 43 | $result = new ezcMvcResult(); 44 | $result->variables["collection"] = $retData; 45 | return $result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /classes/rest_provider.php: -------------------------------------------------------------------------------- 1 | new ezpRestVersionedRoute( 20 | new ezpMvcRailsRoute( 21 | '/content/node/:nodeId/listAtom', 'ezpRestAtomController', 22 | array( 'http-get' => 'collection' ) 23 | ), 1 24 | ), 25 | // @TODO : Make possible to interchange optional params positions 26 | 'ezpList' => new ezpRestVersionedRoute( 27 | new ezpMvcRegexpRoute( 28 | '@^/content/node/(?P\d+)/list(?:/offset/(?P\d+))?(?:/limit/(?P\d+))?(?:/sort/(?P\w+)(?:/(?Pasc|desc))?)?$@', 29 | 'ezpRestContentController', array( 'http-get' => 'list' ) 30 | ), 31 | 1 32 | ), 33 | 'ezpNode' => new ezpRestVersionedRoute( 34 | new ezpMvcRailsRoute( 35 | '/content/node/:nodeId', 'ezpRestContentController', 36 | array( 'http-get' => 'viewContent' ) 37 | ), 38 | 1 39 | ), 40 | 'ezpFieldsByNode' => new ezpRestVersionedRoute( 41 | new ezpMvcRailsRoute( 42 | '/content/node/:nodeId/fields', 'ezpRestContentController', 43 | array( 'http-get' => 'viewFields' ) 44 | ), 45 | 1 46 | ), 47 | 'ezpFieldByNode' => new ezpRestVersionedRoute( 48 | new ezpMvcRailsRoute( 49 | '/content/node/:nodeId/field/:fieldIdentifier', 50 | 'ezpRestContentController', 51 | array( 'http-get' => 'viewField' ) 52 | ), 53 | 1 54 | ), 55 | 'ezpChildrenCount' => new ezpRestVersionedRoute( 56 | new ezpMvcRailsRoute( 57 | '/content/node/:nodeId/childrenCount', 58 | 'ezpRestContentController', 59 | array( 'http-get' => 'countChildren' ) 60 | ), 61 | 1 62 | ), 63 | 'ezpObject' => new ezpRestVersionedRoute( 64 | new ezpMvcRailsRoute( 65 | '/content/object/:objectId', 'ezpRestContentController', 66 | array( 'http-get' => 'viewContent' ) 67 | ), 68 | 1 69 | ), 70 | 'ezpFieldsByObject' => new ezpRestVersionedRoute( 71 | new ezpMvcRailsRoute( 72 | '/content/object/:objectId/fields', 73 | 'ezpRestContentController', 74 | array( 'http-get' => 'viewFields' ) 75 | ), 76 | 1 77 | ), 78 | 'ezpFieldByObject' => new ezpRestVersionedRoute( 79 | new ezpMvcRailsRoute( 80 | '/content/object/:objectId/field/:fieldIdentifier', 81 | 'ezpRestContentController', 82 | array( 'http-get' => 'viewField' ) 83 | ), 84 | 1 85 | ) 86 | ); 87 | return $routes; 88 | } 89 | 90 | /** 91 | * Returns associated with provider view controller 92 | * 93 | * @return ezpRestViewController 94 | */ 95 | public function getViewController() 96 | { 97 | return new ezpRestApiViewController(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/model_test.php: -------------------------------------------------------------------------------- 1 | fields as $fieldName => $field ) 82 | { 83 | self::assertArrayHasKey( $fieldName, $res, "Result does not contain '$fieldName' field, present for given content" ); 84 | $attributeOutput = ezpRestContentModel::attributeOutputData( $field ); 85 | self::assertSame( $attributeOutput, $res[$fieldName], "Result for field '$fieldName' must be the same as returned by ezpRestContentModel::attributeOutputData()" ); 86 | } 87 | } 88 | 89 | /** 90 | * Tests fields data that will be returned to the output 91 | * @group restContentServices 92 | */ 93 | public function testAttributeOutputData() 94 | { 95 | $content = ezpContent::fromNodeId( 2 ); 96 | $expectedKeys = array( 'type', 'identifier', 'value', 'id', 'classattribute_id' ); 97 | 98 | // Browse all the fields and compare result provided by ezpRestContentModel::attributeOutputData() with manually generated data 99 | foreach ( $content->fields as $fieldName => $field ) 100 | { 101 | $aAttributeOutput = ezpRestContentModel::attributeOutputData( $field ); 102 | foreach ( $expectedKeys as $key ) 103 | { 104 | self::assertArrayHasKey( $key, $aAttributeOutput, "Content field must have '$key' metadata" ); 105 | switch ( $key ) 106 | { 107 | case 'type': 108 | self::assertInternalType( PHPUnit_Framework_Constraint_IsType::TYPE_STRING, $aAttributeOutput[$key] ); 109 | self::assertEquals( $field->data_type_string, $aAttributeOutput[$key] ); 110 | break; 111 | 112 | case 'identifier': 113 | self::assertInternalType( PHPUnit_Framework_Constraint_IsType::TYPE_STRING, $aAttributeOutput[$key] ); 114 | self::assertEquals( $field->contentclass_attribute_identifier, $aAttributeOutput[$key] ); 115 | break; 116 | 117 | case 'value': 118 | // Value can be either string or boolean 119 | self::assertTrue( is_string( $aAttributeOutput[$key] ) || is_bool( $aAttributeOutput[$key] ) ); 120 | break; 121 | 122 | case 'id': 123 | self::assertInternalType( PHPUnit_Framework_Constraint_IsType::TYPE_INT, $aAttributeOutput[$key] ); 124 | self::assertEquals( $field->id, $aAttributeOutput[$key] ); 125 | break; 126 | 127 | case 'classattribute_id': 128 | self::assertInternalType( PHPUnit_Framework_Constraint_IsType::TYPE_INT, $aAttributeOutput[$key] ); 129 | self::assertEquals( $field->contentclassattribute_id, $aAttributeOutput[$key] ); 130 | break; 131 | } 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * Data provider for request objects 138 | */ 139 | public function requestObjectProvider() 140 | { 141 | $r1 = new ezpRestRequest(); 142 | $r1->uri = '/api/ezp/content/node/2'; 143 | $r1->contentVariables = array( 'Translation' => 'eng-GB', 'OutputFormat' => 'xhtml' ); 144 | $r1->protocol = 'http-get'; 145 | $r1->host = 'ezpublish.dev'; 146 | 147 | $r2 = clone $r1; 148 | $r2->uri = '/api/ezp/content/node/43'; // Media 149 | $r2->get = array(); 150 | 151 | return array( 152 | array( $r1, 2 ), 153 | array( $r2, 43 ) 154 | ); 155 | } 156 | 157 | /** 158 | * Tests service links for content fields 159 | * @group restContentServices 160 | * @dataProvider requestObjectProvider 161 | * @param ezpRestRequest $request 162 | * @param int $nodeId 163 | */ 164 | public function testFieldsLinksByContent( ezpRestRequest $request, $nodeId ) 165 | { 166 | $mvcConfig = new ezpMvcConfiguration(); 167 | $mvcConfig->runPreRoutingFilters( $request ); 168 | $baseUri = $request->getBaseURI(); 169 | $contentQueryString = $request->getContentQueryString( true ); 170 | 171 | $content = ezpContent::fromNodeId( $nodeId ); 172 | $links = ezpRestContentModel::getFieldsLinksByContent( $content, $request ); 173 | self::assertInternalType( PHPUnit_Framework_Constraint_IsType::TYPE_ARRAY, $links ); 174 | self::assertArrayHasKey( '*', $links, 'Links for fields services must contain a wildcard (*) pointing to a service listing all fields content' ); // * stands for all fields 175 | self::assertEquals( $baseUri.'/fields'.$contentQueryString, $links['*'] ); 176 | 177 | // We must have one entry per field 178 | foreach ( $content->fields as $fieldName => $field ) 179 | { 180 | self::assertArrayHasKey( $fieldName, $links, "Service link missing for $fieldName" ); 181 | self::assertEquals( $baseUri.'/field/'.$fieldName.$contentQueryString, $links[$fieldName], "Wrong service link for $fieldName" ); 182 | } 183 | } 184 | } 185 | ?> 186 | -------------------------------------------------------------------------------- /classes/models/rest_content_model.php: -------------------------------------------------------------------------------- 1 | $content->name, 23 | 'classIdentifier' => $content->classIdentifier, 24 | 'datePublished' => (int)$content->datePublished, 25 | 'dateModified' => (int)$content->dateModified, 26 | 'objectRemoteId' => $content->remote_id, 27 | 'objectId' => (int)$content->id 28 | ); 29 | 30 | return $aMetadata; 31 | } 32 | 33 | /** 34 | * Returns metadata for given content location as array 35 | * @param ezpContentLocation $location 36 | * @return array 37 | */ 38 | public static function getMetadataByLocation( ezpContentLocation $location ) 39 | { 40 | $url = $location->url_alias; 41 | eZURI::transformURI( $url, false, 'full' ); // $url is passed as a reference 42 | 43 | $aMetadata = array( 44 | 'nodeId' => (int)$location->node_id, 45 | 'nodeRemoteId' => $location->remote_id, 46 | 'fullUrl' => $url 47 | ); 48 | 49 | return $aMetadata; 50 | } 51 | 52 | /** 53 | * Returns all locations for provided content as array. 54 | * @param ezpContent $content 55 | * @return array Associative array with following keys : 56 | * - fullUrl => URL for content, including server 57 | * - nodeId => NodeID for location 58 | * - remoteId => RemoteID for location 59 | * - isMain => whether location is main for provided content 60 | */ 61 | public static function getLocationsByContent( ezpContent $content ) 62 | { 63 | $aReturnLocations = array(); 64 | $assignedNodes = $content->assigned_nodes; 65 | foreach ( $assignedNodes as $node ) 66 | { 67 | $location = ezpContentLocation::fromNode( $node ); 68 | $locationData = self::getMetadataByLocation( $location ); 69 | $locationData['isMain'] = $location->is_main; 70 | $aReturnLocations[] = $locationData; 71 | } 72 | 73 | return $aReturnLocations; 74 | } 75 | 76 | /** 77 | * Returns all fields for provided content 78 | * @param ezpContent $content 79 | * @return array Associative array with following keys : 80 | * - type => Field type (datatype string) 81 | * - identifier => Attribute identifier 82 | * - value => String representation of field content 83 | * - id => Attribute numerical ID 84 | * - classattribute_id => Numerical class attribute ID 85 | */ 86 | public static function getFieldsByContent( ezpContent $content ) 87 | { 88 | $aReturnFields = array(); 89 | foreach ( $content->fields as $name => $field ) 90 | { 91 | $aReturnFields[$name] = self::attributeOutputData( $field ); 92 | } 93 | 94 | return $aReturnFields; 95 | } 96 | 97 | /** 98 | * Transforms an ezpContentField in an array representation 99 | * @todo Refactor, this doesn't really belong here. Either in ezpContentField, or in an extend class 100 | * @param ezpContentField $field 101 | * @return array Associative array with following keys : 102 | * - type => Field type (datatype string) 103 | * - identifier => Attribute identifier 104 | * - value => String representation of field content 105 | * - id => Attribute numerical ID 106 | * - classattribute_id => Numerical class attribute ID 107 | */ 108 | public static function attributeOutputData( ezpContentField $field ) 109 | { 110 | // @TODO move to datatype representation layer 111 | switch ( $field->data_type_string ) 112 | { 113 | case 'ezxmltext': 114 | $html = $field->content->attribute( 'output' )->attribute( 'output_text' ); 115 | $attributeValue = array( strip_tags( $html ) ); 116 | break; 117 | case 'ezimage': 118 | $strRepImage = $field->toString(); 119 | $delimPos = strpos( $strRepImage, '|' ); 120 | if ( $delimPos !== false ) 121 | { 122 | $strRepImage = substr( $strRepImage, 0, $delimPos ); 123 | } 124 | $attributeValue = array( $strRepImage ); 125 | break; 126 | default: 127 | $datatypeBlacklist = array_fill_keys( 128 | eZINI::instance()->variable( 'ContentSettings', 'DatatypeBlackListForExternal' ), 129 | true 130 | ); 131 | if ( isset ( $datatypeBlacklist[$field->data_type_string] ) ) 132 | $attributeValue = array( null ); 133 | else 134 | $attributeValue = array( $field->toString() ); 135 | break; 136 | } 137 | 138 | // cleanup values so that the result is consistent: 139 | // - no array if one item 140 | // - false if no values 141 | if ( count( $attributeValue ) == 0 ) 142 | { 143 | $attributeValue = false; 144 | } 145 | else if ( count( $attributeValue ) == 1 ) 146 | { 147 | $attributeValue = current( $attributeValue ); 148 | } 149 | 150 | return array( 151 | 'type' => $field->data_type_string, 152 | 'identifier' => $field->contentclass_attribute_identifier, 153 | 'value' => $attributeValue, 154 | 'id' => (int)$field->id, 155 | 'classattribute_id' => (int)$field->contentclassattribute_id 156 | ); 157 | } 158 | 159 | /** 160 | * Returns fields links for a given content, for a potential future request on a specific field. 161 | * Note that every link provided is based on the current URI. 162 | * So for a content REST request "/content/node/2?Translation=eng-GB", a field link will look like "content/node/2/field/field_identifier?Translation=eng-GB" 163 | * @param ezpContent $content 164 | * @param ezpRestRequest $currentRequest Current REST request object. Needed to build proper links 165 | * @return array Associative array, indexed by field identifier. An additional "*" index is added to request every fields 166 | */ 167 | public static function getFieldsLinksByContent( ezpContent $content, ezpRestRequest $currentRequest ) 168 | { 169 | $links = array(); 170 | $baseUri = $currentRequest->getBaseURI(); 171 | $contentQueryString = $currentRequest->getContentQueryString( true ); 172 | 173 | foreach ( $content->fields as $fieldName => $fieldValue ) 174 | { 175 | $links[$fieldName] = $baseUri.'/field/'.$fieldName.$contentQueryString; 176 | } 177 | $links['*'] = $baseUri.'/fields'.$contentQueryString; 178 | 179 | return $links; 180 | } 181 | 182 | /** 183 | * Returns all children node data, based on the provided criteria object 184 | * @param ezpContentCriteria $c 185 | * @param ezpRestRequest $currentRequest 186 | * @param array $responseGroups Requested ResponseGroups 187 | * @return array 188 | */ 189 | public static function getChildrenList( ezpContentCriteria $c, ezpRestRequest $currentRequest, array $responseGroups = array() ) 190 | { 191 | $aRetData = array(); 192 | $aChildren = ezpContentRepository::query( $c ); 193 | 194 | foreach ( $aChildren as $childNode ) 195 | { 196 | $childEntry = self::getMetadataByContent( $childNode ); 197 | $childEntry = array_merge( $childEntry, self::getMetadataByLocation( $childNode->locations ) ); 198 | 199 | // Add fields with their values if requested 200 | if ( in_array( ezpRestContentController::VIEWLIST_RESPONSEGROUP_FIELDS, $responseGroups ) ) 201 | { 202 | $childEntry['fields'] = array(); 203 | foreach ( $childNode->fields as $fieldName => $field ) 204 | { 205 | $childEntry['fields'][$fieldName] = self::attributeOutputData( $field ); 206 | } 207 | } 208 | 209 | $aRetData[] = $childEntry; 210 | } 211 | 212 | return $aRetData; 213 | } 214 | 215 | /** 216 | * Returns the children count, based on the provided criteria object 217 | * @param ezpContentCriteria $c 218 | * @return int 219 | */ 220 | public static function getChildrenCount( ezpContentCriteria $c ) 221 | { 222 | $count = ezpContentRepository::queryCount( $c ); 223 | return $count; 224 | } 225 | } 226 | 227 | ?> 228 | -------------------------------------------------------------------------------- /classes/controllers/content.php: -------------------------------------------------------------------------------- 1 | setDefaultResponseGroups( array( self::VIEWCONTENT_RESPONSEGROUP_METADATA ) ); 51 | $isNodeRequested = false; 52 | if ( isset( $this->nodeId ) ) 53 | { 54 | $content = ezpContent::fromNodeId( $this->nodeId ); 55 | $isNodeRequested = true; 56 | } 57 | else if ( isset( $this->objectId ) ) 58 | { 59 | $content = ezpContent::fromObjectId( $this->objectId ); 60 | } 61 | 62 | $result = new ezpRestMvcResult(); 63 | 64 | // translation parameter 65 | if ( $this->hasContentVariable( 'Translation' ) ) 66 | $content->setActiveLanguage( $this->getContentVariable( 'Translation' ) ); 67 | 68 | // Handle metadata 69 | if ( $this->hasResponseGroup( self::VIEWCONTENT_RESPONSEGROUP_METADATA ) ) 70 | { 71 | $objectMetadata = ezpRestContentModel::getMetadataByContent( $content ); 72 | if ( $isNodeRequested ) 73 | { 74 | $nodeMetadata = ezpRestContentModel::getMetadataByLocation( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 75 | $objectMetadata = array_merge( $objectMetadata, $nodeMetadata ); 76 | } 77 | $result->variables['metadata'] = $objectMetadata; 78 | } 79 | 80 | // Handle locations if requested 81 | if ( $this->hasResponseGroup( self::VIEWCONTENT_RESPONSEGROUP_LOCATIONS ) ) 82 | { 83 | $result->variables['locations'] = ezpRestContentModel::getLocationsByContent( $content ); 84 | } 85 | 86 | // Handle fields content if requested 87 | if ( $this->hasResponseGroup( self::VIEWCONTENT_RESPONSEGROUP_FIELDS ) ) 88 | { 89 | $result->variables['fields'] = ezpRestContentModel::getFieldsByContent( $content ); 90 | } 91 | 92 | // Add links to fields resources 93 | $result->variables['links'] = ezpRestContentModel::getFieldsLinksByContent( $content, $this->request ); 94 | 95 | if ( $outputFormat = $this->getContentVariable( 'OutputFormat' ) ) 96 | { 97 | $renderer = ezpRestContentRenderer::getRenderer( $outputFormat, $content, $this ); 98 | $result->variables['renderedOutput'] = $renderer->render(); 99 | } 100 | 101 | return $result; 102 | } 103 | 104 | /** 105 | * Handles a content request with fields per object or node id 106 | * Request: GET /api/content/object/XXX/fields 107 | * Request: GET /api/content/node/XXX/fields 108 | * 109 | * @return ezpRestMvcResult 110 | */ 111 | public function doViewFields() 112 | { 113 | $this->setDefaultResponseGroups( array( self::VIEWFIELDS_RESPONSEGROUP_FIELDVALUES ) ); 114 | 115 | $isNodeRequested = false; 116 | if ( isset( $this->nodeId ) ) 117 | { 118 | $content = ezpContent::fromNodeId( $this->nodeId ); 119 | $isNodeRequested = true; 120 | } 121 | else if ( isset( $this->objectId ) ) 122 | { 123 | $content = ezpContent::fromObjectId( $this->objectId ); 124 | } 125 | 126 | $result = new ezpRestMvcResult(); 127 | 128 | // translation parameter 129 | if ( $this->hasContentVariable( 'Translation' ) ) 130 | $content->setActiveLanguage( $this->getContentVariable( 'Translation' ) ); 131 | 132 | // Handle field values 133 | if ( $this->hasResponseGroup( self::VIEWFIELDS_RESPONSEGROUP_FIELDVALUES ) ) 134 | { 135 | $result->variables['fields'] = ezpRestContentModel::getFieldsByContent( $content ); 136 | } 137 | 138 | // Handle object/node metadata 139 | if ( $this->hasResponseGroup( self::VIEWFIELDS_RESPONSEGORUP_METADATA ) ) 140 | { 141 | $objectMetadata = ezpRestContentModel::getMetadataByContent( $content ); 142 | if ( $isNodeRequested ) 143 | { 144 | $nodeMetadata = ezpRestContentModel::getMetadataByLocation( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 145 | $objectMetadata = array_merge( $objectMetadata, $nodeMetadata ); 146 | } 147 | $result->variables['metadata'] = $objectMetadata; 148 | } 149 | 150 | return $result; 151 | } 152 | 153 | /** 154 | * Handles a content unique field request through an object or node ID 155 | * 156 | * Requests: 157 | * - GET /api/content/node/:nodeId/field/:fieldIdentifier 158 | * - GET /api/content/object/:objectId/field/:fieldIdentifier 159 | * 160 | * @return ezpRestMvcResult 161 | */ 162 | public function doViewField() 163 | { 164 | $this->setDefaultResponseGroups( array( self::VIEWFIELDS_RESPONSEGROUP_FIELDVALUES ) ); 165 | 166 | $isNodeRequested = false; 167 | if ( isset( $this->nodeId ) ) 168 | { 169 | $isNodeRequested = true; 170 | $content = ezpContent::fromNodeId( $this->nodeId ); 171 | } 172 | else if ( isset( $this->objectId ) ) 173 | { 174 | $content = ezpContent::fromObjectId( $this->objectId ); 175 | } 176 | 177 | if ( !isset( $content->fields->{$this->fieldIdentifier} ) ) 178 | { 179 | throw new ezpContentFieldNotFoundException( "'$this->fieldIdentifier' field is not available for this content." ); 180 | } 181 | 182 | // Translation parameter 183 | if ( $this->hasContentVariable( 'Translation' ) ) 184 | $content->setActiveLanguage( $this->getContentVariable( 'Translation' ) ); 185 | 186 | $result = new ezpRestMvcResult(); 187 | 188 | // Field data 189 | if ( $this->hasResponseGroup( self::VIEWFIELDS_RESPONSEGROUP_FIELDVALUES ) ) 190 | { 191 | $result->variables['fields'][$this->fieldIdentifier] = ezpRestContentModel::attributeOutputData( $content->fields->{$this->fieldIdentifier} ); 192 | } 193 | 194 | // Handle object/node metadata 195 | if ( $this->hasResponseGroup( self::VIEWFIELDS_RESPONSEGORUP_METADATA ) ) 196 | { 197 | $objectMetadata = ezpRestContentModel::getMetadataByContent( $content, $isNodeRequested ); 198 | if ( $isNodeRequested ) 199 | { 200 | $nodeMetadata = ezpRestContentModel::getMetadataByLocation( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 201 | $objectMetadata = array_merge( $objectMetadata, $nodeMetadata ); 202 | } 203 | $result->variables['metadata'] = $objectMetadata; 204 | } 205 | 206 | return $result; 207 | } 208 | 209 | /** 210 | * Handles a content request to view a node children list 211 | * Requests : 212 | * - GET /api/v1/content/node//list(/offset//limit//sort//) 213 | * - Every parameters in parenthesis are optional. However, to have offset/limit and sort, the order is mandatory 214 | * (you can't provide sorting params before limit params). This is due to a limitation in the regexp route. 215 | * - Following requests are valid : 216 | * - /api/ezp/content/node/2/list/sort/name => will display 10 (default limit) children of node 2, sorted by ascending name 217 | * - /api/ezp/content/node/2/list/limit/50/sort/published/desc => will display 50 children of node 2, sorted by descending publishing date 218 | * - /api/ezp/content/node/2/list/offset/100/limit/50/sort/published/desc => will display 50 children of node 2 starting from offset 100, sorted by descending publishing date 219 | * 220 | * Default values : 221 | * - offset : 0 222 | * - limit : 10 223 | * - sortType : asc 224 | */ 225 | public function doList() 226 | { 227 | $this->setDefaultResponseGroups( array( self::VIEWLIST_RESPONSEGROUP_METADATA ) ); 228 | $result = new ezpRestMvcResult(); 229 | $crit = new ezpContentCriteria(); 230 | 231 | // Location criteria 232 | // Hmm, the following sequence is too long... 233 | $crit->accept[] = ezpContentCriteria::location()->subtree( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 234 | $crit->accept[] = ezpContentCriteria::depth( 1 ); // Fetch children only 235 | 236 | // Limit criteria 237 | $offset = isset( $this->offset ) ? $this->offset : 0; 238 | $limit = isset( $this->limit ) ? $this->limit : 10; 239 | $crit->accept[] = ezpContentCriteria::limit()->offset( $offset )->limit( $limit ); 240 | 241 | // Sort criteria 242 | if ( isset( $this->sortKey ) ) 243 | { 244 | $sortOrder = isset( $this->sortType ) ? $this->sortType : 'asc'; 245 | $crit->accept[] = ezpContentCriteria::sorting( $this->sortKey, $sortOrder ); 246 | } 247 | 248 | $result->variables['childrenNodes'] = ezpRestContentModel::getChildrenList( $crit, $this->request, $this->getResponseGroups() ); 249 | // REST links to children nodes 250 | // Little dirty since this should belong to the model layer, but I don't want to pass the router nor the full controller to the model 251 | $contentQueryString = $this->request->getContentQueryString( true ); 252 | for ( $i = 0, $iMax = count( $result->variables['childrenNodes'] ); $i < $iMax; ++$i ) 253 | { 254 | $linkURI = $this->getRouter()->generateUrl( 'ezpNode', array( 'nodeId' => $result->variables['childrenNodes'][$i]['nodeId'] ) ); 255 | $result->variables['childrenNodes'][$i]['link'] = $this->request->getHostURI().$linkURI.$contentQueryString; 256 | } 257 | 258 | // Handle Metadata 259 | if ( $this->hasResponseGroup( self::VIEWLIST_RESPONSEGROUP_METADATA ) ) 260 | { 261 | $childrenCount = ezpRestContentModel::getChildrenCount( $crit ); 262 | $result->variables['metadata'] = array( 263 | 'childrenCount' => $childrenCount, 264 | 'parentNodeId' => $this->nodeId 265 | ); 266 | 267 | } 268 | 269 | return $result; 270 | } 271 | 272 | /** 273 | * Counts children of a given node 274 | * Request : 275 | * - GET /api/ezp/content/node/childrenCount 276 | */ 277 | public function doCountChildren() 278 | { 279 | $this->setDefaultResponseGroups( array( self::VIEWLIST_RESPONSEGROUP_METADATA ) ); 280 | $result = new ezpRestMvcResult(); 281 | 282 | if ( $this->hasResponseGroup( self::VIEWLIST_RESPONSEGROUP_METADATA ) ) 283 | { 284 | $crit = new ezpContentCriteria(); 285 | $crit->accept[] = ezpContentCriteria::location()->subtree( ezpContentLocation::fetchByNodeId( $this->nodeId ) ); 286 | $crit->accept[] = ezpContentCriteria::depth( 1 ); // Fetch children only 287 | $childrenCount = ezpRestContentModel::getChildrenCount( $crit ); 288 | $result->variables['metadata'] = array( 289 | 'childrenCount' => $childrenCount, 290 | 'parentNodeId' => $this->nodeId 291 | ); 292 | } 293 | 294 | return $result; 295 | } 296 | } 297 | ?> 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 1999-2014 eZ Systems AS. All rights reserved. 2 | This source code is provided under the following license: 3 | 4 | 5 | GNU GENERAL PUBLIC LICENSE 6 | Version 2, June 1991 7 | 8 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 9 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 10 | Everyone is permitted to copy and distribute verbatim copies 11 | of this license document, but changing it is not allowed. 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | License is intended to guarantee your freedom to share and change free 18 | software--to make sure the software is free for all its users. This 19 | General Public License applies to most of the Free Software 20 | Foundation's software and to any other program whose authors commit to 21 | using it. (Some other Free Software Foundation software is covered by 22 | the GNU Lesser General Public License instead.) You can apply it to 23 | your programs, too. 24 | 25 | When we speak of free software, we are referring to freedom, not 26 | price. Our General Public Licenses are designed to make sure that you 27 | have the freedom to distribute copies of free software (and charge for 28 | this service if you wish), that you receive source code or can get it 29 | if you want it, that you can change the software or use pieces of it 30 | in new free programs; and that you know you can do these things. 31 | 32 | To protect your rights, we need to make restrictions that forbid 33 | anyone to deny you these rights or to ask you to surrender the rights. 34 | These restrictions translate to certain responsibilities for you if you 35 | distribute copies of the software, or if you modify it. 36 | 37 | For example, if you distribute copies of such a program, whether 38 | gratis or for a fee, you must give the recipients all the rights that 39 | you have. You must make sure that they, too, receive or can get the 40 | source code. And you must show them these terms so they know their 41 | rights. 42 | 43 | We protect your rights with two steps: (1) copyright the software, and 44 | (2) offer you this license which gives you legal permission to copy, 45 | distribute and/or modify the software. 46 | 47 | Also, for each author's protection and ours, we want to make certain 48 | that everyone understands that there is no warranty for this free 49 | software. If the software is modified by someone else and passed on, we 50 | want its recipients to know that what they have is not the original, so 51 | that any problems introduced by others will not reflect on the original 52 | authors' reputations. 53 | 54 | Finally, any free program is threatened constantly by software 55 | patents. We wish to avoid the danger that redistributors of a free 56 | program will individually obtain patent licenses, in effect making the 57 | program proprietary. To prevent this, we have made it clear that any 58 | patent must be licensed for everyone's free use or not licensed at all. 59 | 60 | The precise terms and conditions for copying, distribution and 61 | modification follow. 62 | 63 | GNU GENERAL PUBLIC LICENSE 64 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 65 | 66 | 0. This License applies to any program or other work which contains 67 | a notice placed by the copyright holder saying it may be distributed 68 | under the terms of this General Public License. The "Program", below, 69 | refers to any such program or work, and a "work based on the Program" 70 | means either the Program or any derivative work under copyright law: 71 | that is to say, a work containing the Program or a portion of it, 72 | either verbatim or with modifications and/or translated into another 73 | language. (Hereinafter, translation is included without limitation in 74 | the term "modification".) Each licensee is addressed as "you". 75 | 76 | Activities other than copying, distribution and modification are not 77 | covered by this License; they are outside its scope. The act of 78 | running the Program is not restricted, and the output from the Program 79 | is covered only if its contents constitute a work based on the 80 | Program (independent of having been made by running the Program). 81 | Whether that is true depends on what the Program does. 82 | 83 | 1. You may copy and distribute verbatim copies of the Program's 84 | source code as you receive it, in any medium, provided that you 85 | conspicuously and appropriately publish on each copy an appropriate 86 | copyright notice and disclaimer of warranty; keep intact all the 87 | notices that refer to this License and to the absence of any warranty; 88 | and give any other recipients of the Program a copy of this License 89 | along with the Program. 90 | 91 | You may charge a fee for the physical act of transferring a copy, and 92 | you may at your option offer warranty protection in exchange for a fee. 93 | 94 | 2. You may modify your copy or copies of the Program or any portion 95 | of it, thus forming a work based on the Program, and copy and 96 | distribute such modifications or work under the terms of Section 1 97 | above, provided that you also meet all of these conditions: 98 | 99 | a) You must cause the modified files to carry prominent notices 100 | stating that you changed the files and the date of any change. 101 | 102 | b) You must cause any work that you distribute or publish, that in 103 | whole or in part contains or is derived from the Program or any 104 | part thereof, to be licensed as a whole at no charge to all third 105 | parties under the terms of this License. 106 | 107 | c) If the modified program normally reads commands interactively 108 | when run, you must cause it, when started running for such 109 | interactive use in the most ordinary way, to print or display an 110 | announcement including an appropriate copyright notice and a 111 | notice that there is no warranty (or else, saying that you provide 112 | a warranty) and that users may redistribute the program under 113 | these conditions, and telling the user how to view a copy of this 114 | License. (Exception: if the Program itself is interactive but 115 | does not normally print such an announcement, your work based on 116 | the Program is not required to print an announcement.) 117 | 118 | These requirements apply to the modified work as a whole. If 119 | identifiable sections of that work are not derived from the Program, 120 | and can be reasonably considered independent and separate works in 121 | themselves, then this License, and its terms, do not apply to those 122 | sections when you distribute them as separate works. But when you 123 | distribute the same sections as part of a whole which is a work based 124 | on the Program, the distribution of the whole must be on the terms of 125 | this License, whose permissions for other licensees extend to the 126 | entire whole, and thus to each and every part regardless of who wrote it. 127 | 128 | Thus, it is not the intent of this section to claim rights or contest 129 | your rights to work written entirely by you; rather, the intent is to 130 | exercise the right to control the distribution of derivative or 131 | collective works based on the Program. 132 | 133 | In addition, mere aggregation of another work not based on the Program 134 | with the Program (or with a work based on the Program) on a volume of 135 | a storage or distribution medium does not bring the other work under 136 | the scope of this License. 137 | 138 | 3. You may copy and distribute the Program (or a work based on it, 139 | under Section 2) in object code or executable form under the terms of 140 | Sections 1 and 2 above provided that you also do one of the following: 141 | 142 | a) Accompany it with the complete corresponding machine-readable 143 | source code, which must be distributed under the terms of Sections 144 | 1 and 2 above on a medium customarily used for software interchange; or, 145 | 146 | b) Accompany it with a written offer, valid for at least three 147 | years, to give any third party, for a charge no more than your 148 | cost of physically performing source distribution, a complete 149 | machine-readable copy of the corresponding source code, to be 150 | distributed under the terms of Sections 1 and 2 above on a medium 151 | customarily used for software interchange; or, 152 | 153 | c) Accompany it with the information you received as to the offer 154 | to distribute corresponding source code. (This alternative is 155 | allowed only for noncommercial distribution and only if you 156 | received the program in object code or executable form with such 157 | an offer, in accord with Subsection b above.) 158 | 159 | The source code for a work means the preferred form of the work for 160 | making modifications to it. For an executable work, complete source 161 | code means all the source code for all modules it contains, plus any 162 | associated interface definition files, plus the scripts used to 163 | control compilation and installation of the executable. However, as a 164 | special exception, the source code distributed need not include 165 | anything that is normally distributed (in either source or binary 166 | form) with the major components (compiler, kernel, and so on) of the 167 | operating system on which the executable runs, unless that component 168 | itself accompanies the executable. 169 | 170 | If distribution of executable or object code is made by offering 171 | access to copy from a designated place, then offering equivalent 172 | access to copy the source code from the same place counts as 173 | distribution of the source code, even though third parties are not 174 | compelled to copy the source along with the object code. 175 | 176 | 4. You may not copy, modify, sublicense, or distribute the Program 177 | except as expressly provided under this License. Any attempt 178 | otherwise to copy, modify, sublicense or distribute the Program is 179 | void, and will automatically terminate your rights under this License. 180 | However, parties who have received copies, or rights, from you under 181 | this License will not have their licenses terminated so long as such 182 | parties remain in full compliance. 183 | 184 | 5. You are not required to accept this License, since you have not 185 | signed it. However, nothing else grants you permission to modify or 186 | distribute the Program or its derivative works. These actions are 187 | prohibited by law if you do not accept this License. Therefore, by 188 | modifying or distributing the Program (or any work based on the 189 | Program), you indicate your acceptance of this License to do so, and 190 | all its terms and conditions for copying, distributing or modifying 191 | the Program or works based on it. 192 | 193 | 6. Each time you redistribute the Program (or any work based on the 194 | Program), the recipient automatically receives a license from the 195 | original licensor to copy, distribute or modify the Program subject to 196 | these terms and conditions. You may not impose any further 197 | restrictions on the recipients' exercise of the rights granted herein. 198 | You are not responsible for enforcing compliance by third parties to 199 | this License. 200 | 201 | 7. If, as a consequence of a court judgment or allegation of patent 202 | infringement or for any other reason (not limited to patent issues), 203 | conditions are imposed on you (whether by court order, agreement or 204 | otherwise) that contradict the conditions of this License, they do not 205 | excuse you from the conditions of this License. If you cannot 206 | distribute so as to satisfy simultaneously your obligations under this 207 | License and any other pertinent obligations, then as a consequence you 208 | may not distribute the Program at all. For example, if a patent 209 | license would not permit royalty-free redistribution of the Program by 210 | all those who receive copies directly or indirectly through you, then 211 | the only way you could satisfy both it and this License would be to 212 | refrain entirely from distribution of the Program. 213 | 214 | If any portion of this section is held invalid or unenforceable under 215 | any particular circumstance, the balance of the section is intended to 216 | apply and the section as a whole is intended to apply in other 217 | circumstances. 218 | 219 | It is not the purpose of this section to induce you to infringe any 220 | patents or other property right claims or to contest validity of any 221 | such claims; this section has the sole purpose of protecting the 222 | integrity of the free software distribution system, which is 223 | implemented by public license practices. Many people have made 224 | generous contributions to the wide range of software distributed 225 | through that system in reliance on consistent application of that 226 | system; it is up to the author/donor to decide if he or she is willing 227 | to distribute software through any other system and a licensee cannot 228 | impose that choice. 229 | 230 | This section is intended to make thoroughly clear what is believed to 231 | be a consequence of the rest of this License. 232 | 233 | 8. If the distribution and/or use of the Program is restricted in 234 | certain countries either by patents or by copyrighted interfaces, the 235 | original copyright holder who places the Program under this License 236 | may add an explicit geographical distribution limitation excluding 237 | those countries, so that distribution is permitted only in or among 238 | countries not thus excluded. In such case, this License incorporates 239 | the limitation as if written in the body of this License. 240 | 241 | 9. The Free Software Foundation may publish revised and/or new versions 242 | of the General Public License from time to time. Such new versions will 243 | be similar in spirit to the present version, but may differ in detail to 244 | address new problems or concerns. 245 | 246 | Each version is given a distinguishing version number. If the Program 247 | specifies a version number of this License which applies to it and "any 248 | later version", you have the option of following the terms and conditions 249 | either of that version or of any later version published by the Free 250 | Software Foundation. If the Program does not specify a version number of 251 | this License, you may choose any version ever published by the Free Software 252 | Foundation. 253 | 254 | 10. If you wish to incorporate parts of the Program into other free 255 | programs whose distribution conditions are different, write to the author 256 | to ask for permission. For software which is copyrighted by the Free 257 | Software Foundation, write to the Free Software Foundation; we sometimes 258 | make exceptions for this. Our decision will be guided by the two goals 259 | of preserving the free status of all derivatives of our free software and 260 | of promoting the sharing and reuse of software generally. 261 | 262 | NO WARRANTY 263 | 264 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 265 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 266 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 267 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 268 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 269 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 270 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 271 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 272 | REPAIR OR CORRECTION. 273 | 274 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 275 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 276 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 277 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 278 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 279 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 280 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 281 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 282 | POSSIBILITY OF SUCH DAMAGES. 283 | 284 | END OF TERMS AND CONDITIONS 285 | 286 | How to Apply These Terms to Your New Programs 287 | 288 | If you develop a new program, and you want it to be of the greatest 289 | possible use to the public, the best way to achieve this is to make it 290 | free software which everyone can redistribute and change under these terms. 291 | 292 | To do so, attach the following notices to the program. It is safest 293 | to attach them to the start of each source file to most effectively 294 | convey the exclusion of warranty; and each file should have at least 295 | the "copyright" line and a pointer to where the full notice is found. 296 | 297 | 298 | Copyright (C) 299 | 300 | This program is free software; you can redistribute it and/or modify 301 | it under the terms of the GNU General Public License as published by 302 | the Free Software Foundation; either version 2 of the License, or 303 | (at your option) any later version. 304 | 305 | This program is distributed in the hope that it will be useful, 306 | but WITHOUT ANY WARRANTY; without even the implied warranty of 307 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 308 | GNU General Public License for more details. 309 | 310 | You should have received a copy of the GNU General Public License along 311 | with this program; if not, write to the Free Software Foundation, Inc., 312 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 313 | 314 | Also add information on how to contact you by electronic and paper mail. 315 | 316 | If the program is interactive, make it output a short notice like this 317 | when it starts in an interactive mode: 318 | 319 | Gnomovision version 69, Copyright (C) year name of author 320 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 321 | This is free software, and you are welcome to redistribute it 322 | under certain conditions; type `show c' for details. 323 | 324 | The hypothetical commands `show w' and `show c' should show the appropriate 325 | parts of the General Public License. Of course, the commands you use may 326 | be called something other than `show w' and `show c'; they could even be 327 | mouse-clicks or menu items--whatever suits your program. 328 | 329 | You should also get your employer (if you work as a programmer) or your 330 | school, if any, to sign a "copyright disclaimer" for the program, if 331 | necessary. Here is a sample; alter the names: 332 | 333 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 334 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 335 | 336 | , 1 April 1989 337 | Ty Coon, President of Vice 338 | 339 | This General Public License does not permit incorporating your program into 340 | proprietary programs. If your program is a subroutine library, you may 341 | consider it more useful to permit linking proprietary applications with the 342 | library. If this is what you want to do, use the GNU Lesser General 343 | Public License instead of this License. 344 | --------------------------------------------------------------------------------